Repository: nicoespeon/abracadabra
Pull request #725 (2022-10-09 12:18:36)

[Proposal] Change function signature
Related to https://github.com/nicoespeon/abracadabra/issues/341

Hi @nicoespeon 

Here is a new refactoring proposal: Change function signature.

At the moment we are able to change "parameters arguments" of a function.

Cases:

- Given a function with two parameters:

```javascript
function add(param: number, num2: number) {
  return param + num2;
}

add(1, 200);
```
- Expect to change parameters and arguments orders

```javascript
function add(num2: number, param: number) {
  return param + num2;
}

add(200, 1);
``` 


![function-arguments-change](https://user-images.githubusercontent.com/8685132/194755839-5097690a-2609-4aab-9381-48cb4dee5eed.gif)



> Question: Why "Change Signature" open a Tab for order definition?
> Answer: I not able to create a "Popup" for asking orders. vscode does not contain a (at the momento) API to give us that functionality. So I render a "Webview" as a sidebar. I found that [Color Picker](https://marketplace.visualstudio.com/items?itemName=anseki.vscode-color) has a Popup but I don't want to look how they created it (lazy boy :P)

- Given a function with second param as optional
```javascript
function add(paramRequired, paramOptional: number = 1) {
  return paramRequired + paramOptional;
}

add(200);
```
- Expect to put `undefined` as a value when there are no value in call expression

```javascript
function add(paramOptional: number = 1, paramRequired) {
  return paramRequired + paramOptional;
}

add(undefined, 200);
```

There are more cases in test case.

- Given a function with value and callback
```javascript
function repeat(f, n) {
  return Array(n)
    .fill(null)
    .map((_, i) => f(i));
}

const squares = repeat((i) => i * i, 5);
```
- Expect to change argument order even if there a callback
```javascript
function repeat(n, f) {
  return Array(n)
    .fill(null)
    .map((_, i) => f(i));
}

const squares = repeat(5, (i) => i * i);
```


### What about modules

I develop the funcionality to search (using vscode Find References) references in base code and change arguments orders based on user definition

![modules](https://user-images.githubusercontent.com/8685132/194756418-5f9b5187-79f8-42d8-a7ea-c64713ddb30b.gif)

### To Fix

For modules imported with aliases would not found a references and the changes will break app -> need to look how to prevent changes when there are aliases for a exported funcion

### Next?

Maybe we can add functionalities like:
- Remove param
- Add new param with a value or default value
Feedback are welcome :) 



Patch:
diff --git a/src/editor/adapters/attempting-editor.ts b/src/editor/adapters/attempting-editor.ts
index c97b0cd9..8a48930d 100644
--- a/src/editor/adapters/attempting-editor.ts
+++ b/src/editor/adapters/attempting-editor.ts
@@ -1,3 +1,4 @@
+import { CodeReference } from "../code-reference";
 import {
   Choice,
   Code,
@@ -5,8 +6,9 @@ import {
   Editor,
   ErrorReason,
   Modification,
-  RelativePath
+  SelectedPosition
 } from "../editor";
+import { Path } from "../path";
 import { Position } from "../position";
 import { Selection } from "../selection";
 
@@ -15,7 +17,7 @@ export class AttemptingEditor implements Editor {
 
   constructor(private editor: Editor, private expectedReason: ErrorReason) {}
 
-  workspaceFiles(): Promise<RelativePath[]> {
+  workspaceFiles(): Promise<Path[]> {
     return this.editor.workspaceFiles();
   }
 
@@ -23,7 +25,7 @@ export class AttemptingEditor implements Editor {
     return this.editor.code;
   }
 
-  codeOf(path: RelativePath): Promise<Code> {
+  codeOf(path: Path): Promise<Code> {
     return this.editor.codeOf(path);
   }
 
@@ -35,7 +37,7 @@ export class AttemptingEditor implements Editor {
     return this.editor.write(code, newCursorPosition);
   }
 
-  writeIn(path: RelativePath, code: Code): Promise<void> {
+  writeIn(path: Path, code: Code): Promise<void> {
     return this.editor.writeIn(path, code);
   }
 
@@ -78,4 +80,15 @@ export class AttemptingEditor implements Editor {
   moveCursorTo(position: Position): Promise<void> {
     return this.editor.moveCursorTo(position);
   }
+
+  getSelectionReferences(selection: Selection): Promise<CodeReference[]> {
+    return this.editor.getSelectionReferences(selection);
+  }
+
+  async askForPositions(
+    params: SelectedPosition[],
+    onConfirm: (positions: SelectedPosition[]) => Promise<void>
+  ): Promise<void> {
+    await this.editor.askForPositions(params, onConfirm);
+  }
 }
diff --git a/src/editor/adapters/in-memory-editor.ts b/src/editor/adapters/in-memory-editor.ts
index 5d774896..14077578 100644
--- a/src/editor/adapters/in-memory-editor.ts
+++ b/src/editor/adapters/in-memory-editor.ts
@@ -6,10 +6,12 @@ import {
   ErrorReason,
   Choice,
   Result,
-  RelativePath
+  SelectedPosition
 } from "../editor";
 import { Selection } from "../selection";
 import { Position } from "../position";
+import { Path } from "../path";
+import { CodeReference } from "../code-reference";
 
 const LINE_SEPARATOR = "\n";
 const CHARS_SEPARATOR = "";
@@ -21,14 +23,15 @@ const SELECTION_END = "[end]";
 export class InMemoryEditor implements Editor {
   private codeMatrix: CodeMatrix = [];
   private _selection: Selection = Selection.cursorAt(0, 0);
-  private otherFiles = new Map<RelativePath, Editor>();
+  private otherFiles = new Map<Path, Editor>();
+  private userChoices: Choice<unknown>[] = [];
 
   constructor(code: Code, position: Position = new Position(0, 0)) {
     this.setCodeMatrix(code);
     this.setSelectionFromCursor(code, Selection.cursorAtPosition(position));
   }
 
-  async workspaceFiles(): Promise<RelativePath[]> {
+  async workspaceFiles(): Promise<Path[]> {
     return Array.from(this.otherFiles.keys());
   }
 
@@ -36,7 +39,7 @@ export class InMemoryEditor implements Editor {
     return this.read(this.codeMatrix);
   }
 
-  async codeOf(path: RelativePath): Promise<Code> {
+  async codeOf(path: Path): Promise<Code> {
     const otherFile = this.otherFiles.get(path);
     if (!otherFile) return "";
 
@@ -59,7 +62,7 @@ export class InMemoryEditor implements Editor {
     return Promise.resolve();
   }
 
-  async writeIn(path: RelativePath, code: Code): Promise<void> {
+  async writeIn(path: Path, code: Code): Promise<void> {
     this.otherFiles.set(path, new InMemoryEditor(code));
   }
 
@@ -204,6 +207,87 @@ export class InMemoryEditor implements Editor {
   removeLine(line: number): void {
     this.codeMatrix.splice(line, 1);
   }
+
+  async getSelectionReferences(selection: Selection): Promise<CodeReference[]> {
+    const { start } = selection;
+    const endOfFunctionDef = {
+      OPEN_PARENTHESIS: "(",
+      SPACE_BETWEEN_OPEN_PARENTHESIS: " ",
+      ARROW_DEFINITION: "="
+    };
+
+    const line = this.codeMatrix[start.line];
+    const startLine = line.slice(start.character);
+
+    const endLineIndex = startLine.findIndex((item) => {
+      return Object.values(endOfFunctionDef).includes(item);
+    });
+
+    const functionReferences = line
+      .slice(start.character, start.character + endLineIndex)
+      .join("");
+
+    const array = Array.from(this.otherFiles, ([filename, editor]) => ({
+      filename,
+      editor
+    })).filter(({ editor }) => {
+      return editor.code.includes(functionReferences);
+    });
+
+    const references: CodeReference[] = [];
+
+    array.forEach((obj) => {
+      const codeMatrix = this.createPos(obj.editor.code, functionReferences);
+
+      codeMatrix.forEach((pos) => {
+        references.push(
+          new CodeReference(
+            obj.filename,
+            new Selection([pos.row, pos.col], [pos.row, pos.col])
+          )
+        );
+      });
+    });
+
+    return references;
+  }
+
+  private createPos(str: string, wordToSearch: string) {
+    const splittedCode: string[][] = str.split("\n").map((e) => [...e]);
+    const items: {
+      word: string;
+      row: number;
+      col: number;
+    }[] = [];
+
+    splittedCode.forEach((arr, row) => {
+      const word = arr.join("");
+      if (word.includes(wordToSearch)) {
+        const post = arr.map((_char, col) => ({
+          word,
+          row: row + 1,
+          col: col + 1
+        }));
+
+        items.push(...post);
+      }
+    });
+
+    return items.filter(
+      (v, i, a) => a.findIndex((v2) => v2.word === v.word) === i
+    );
+  }
+
+  async askForPositions(
+    _params: SelectedPosition[],
+    onConfirm: (positions: SelectedPosition[]) => Promise<void>
+  ): Promise<void> {
+    await onConfirm(this.userChoices as SelectedPosition[]);
+  }
+
+  public saveUserChoices<T>(choice: Choice<T>) {
+    this.userChoices.push(choice);
+  }
 }
 
 type CodeMatrix = Line[];
diff --git a/src/editor/adapters/vscode-editor.ts b/src/editor/adapters/vscode-editor.ts
index c8c63259..11f5ff90 100644
--- a/src/editor/adapters/vscode-editor.ts
+++ b/src/editor/adapters/vscode-editor.ts
@@ -13,18 +13,21 @@ import {
 } from "../editor";
 import { Selection } from "../selection";
 import { Position } from "../position";
-import { AbsolutePath, RelativePath } from "../path";
+import { AbsolutePath, Path } from "../path";
+import { CodeReference } from "../code-reference";
+import { SelectedPosition } from "../editor";
 
 export class VSCodeEditor implements Editor {
   private editor: vscode.TextEditor;
   private document: vscode.TextDocument;
+  public static panel: vscode.WebviewPanel | null = null;
 
   constructor(editor: vscode.TextEditor) {
     this.editor = editor;
     this.document = editor.document;
   }
 
-  async workspaceFiles(): Promise<RelativePath[]> {
+  async workspaceFiles(): Promise<Path[]> {
     const uris = await this.findFileUris();
 
     return uris
@@ -46,11 +49,12 @@ export class VSCodeEditor implements Editor {
     return this.document.getText();
   }
 
-  async codeOf(path: RelativePath): Promise<Code> {
+  async codeOf(path: Path): Promise<Code> {
     const fileUri = this.fileUriAt(path);
-    const file = await vscode.workspace.fs.readFile(fileUri);
+    // Get file content even if user does not save last changes
+    const doc = await vscode.workspace.openTextDocument(fileUri);
 
-    return file.toString();
+    return doc.getText();
   }
 
   get selection(): Selection {
@@ -83,7 +87,7 @@ export class VSCodeEditor implements Editor {
     }
   }
 
-  async writeIn(path: RelativePath, code: Code): Promise<void> {
+  async writeIn(path: Path, code: Code): Promise<void> {
     const fileUri = this.fileUriAt(path);
     await VSCodeEditor.ensureFileExists(fileUri);
 
@@ -110,8 +114,9 @@ export class VSCodeEditor implements Editor {
     }
   }
 
-  protected fileUriAt(path: RelativePath): vscode.Uri {
+  protected fileUriAt(path: Path): vscode.Uri {
     const filePath = path.absoluteFrom(this.document.uri.path);
+
     return this.document.uri.with({ path: filePath.value });
   }
 
@@ -182,6 +187,81 @@ export class VSCodeEditor implements Editor {
     this.editor.selection = toVSCodeCursor(position);
     return Promise.resolve();
   }
+
+  async getSelectionReferences(selection: Selection): Promise<CodeReference[]> {
+    const locations = (await vscode.commands.executeCommand(
+      "vscode.executeReferenceProvider",
+      this.document.uri,
+      selection.start
+    )) as vscode.Location[];
+
+    return locations.map((loc) => {
+      const start = loc.range.start;
+      const end = loc.range.end;
+
+      const path = new AbsolutePath(loc.uri.path);
+
+      const codeReferenceSelection = new Selection(
+        [start.line + 1, start.character],
+        [end.line + 1, end.character]
+      );
+
+      return new CodeReference(path, codeReferenceSelection);
+    });
+  }
+
+  async askForPositions(
+    params: SelectedPosition[],
+    onConfirm: (positions: SelectedPosition[]) => Promise<void>
+  ): Promise<void> {
+    if (VSCodeEditor.panel !== null) {
+      VSCodeEditor.panel.dispose();
+    }
+
+    VSCodeEditor.panel = vscode.window.createWebviewPanel(
+      "changeSignature",
+      "Change function signature",
+      vscode.ViewColumn.Beside,
+      {}
+    );
+
+    VSCodeEditor.panel.webview.options = {
+      enableScripts: true
+    };
+    VSCodeEditor.panel.webview.html = getParamsPositionWebViewContent(
+      params,
+      VSCodeEditor.panel.webview
+    );
+
+    VSCodeEditor.panel.webview.onDidReceiveMessage(
+      async (message: Record<string, string>) => {
+        const values = JSON.parse(message.values) as {
+          label: string;
+          startAt: number;
+          endAt: number;
+        }[];
+
+        const result: SelectedPosition[] = values.map((result) => {
+          return {
+            label: result.label,
+            value: {
+              startAt: result.startAt,
+              endAt: result.endAt
+            }
+          };
+        });
+
+        await onConfirm(result);
+        VSCodeEditor.panel?.dispose();
+        VSCodeEditor.panel = null;
+      },
+      undefined
+    );
+
+    VSCodeEditor.panel.onDidDispose(() => {
+      VSCodeEditor.panel = null;
+    });
+  }
 }
 
 function createSelectionFromVSCode(
@@ -213,3 +293,161 @@ function toVSCodeCommand(command: Command): string {
       return "";
   }
 }
+
+function getParamsPositionWebViewContent(
+  params: SelectedPosition[],
+  _webview: vscode.Webview
+): string {
+  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>
+    `;
+  });
+
+  const html = `
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <style>
+      table {
+        font-family: arial, sans-serif;
+        border-collapse: collapse;
+      }
+
+      td,
+      th {
+        border: 1px solid #dddddd;
+        text-align: left;
+        padding: 8px;
+      }
+
+      th:last-child {
+        border-top-color: transparent;
+        border-right-color: transparent;
+      }
+
+      .up,
+      .down {
+        cursor: pointer;
+        display: inline-block;
+        width: 8px;
+        margin: 0 0.7rem;
+        font-size: 1.2rem;
+      }
+
+      .up:after {
+        content: "▲";
+      }
+
+      .up:hover:after {
+        color: #625e5e;
+      }
+
+      .down:after {
+        content: "▼";
+      }
+
+      .down:hover:after {
+        color: #625e5e;
+      }
+
+      button {
+        border: 1px solid transparent;
+        border-radius: 5px;
+        line-height: 1.25rem;
+        outline: none;
+        text-align: center;
+        white-space: nowrap;
+        display: inline-block;
+        text-decoration: none;
+        background: #1a85ff;
+        padding: 4px;
+        color: white;
+        font-size: 14px;
+      }
+
+      button:hover {
+        cursor: pointer;
+        color: white;
+      }
+    </style>
+  </head>
+
+  <body>
+    <h4>Parameters</h4>
+    <table>
+      <thead>
+        <tr>
+          <th>Name</th>
+          <th></th>
+        </tr>
+      </thead>
+
+      <tbody id="params">
+        ${paramsTrValues.join("")}
+      </tbody>
+      <tfoot>
+        <tr>
+          <td colspan="2" style="text-align: center">
+            <button id="confirm">Confirm</button>
+          </td>
+        </tr>
+      </tfoot>
+    </table>
+
+    <div class="btn-wrapper"></div>
+
+    <script>
+      const vscode = acquireVsCodeApi();
+      const startValues = document.querySelectorAll("#params .params-name");
+      function moveUp(element) {
+        if (element.previousElementSibling)
+          element.parentNode.insertBefore(
+            element,
+            element.previousElementSibling
+          );
+      }
+
+      function moveDown(element) {
+        if (element.nextElementSibling)
+          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);
+      });
+
+      document.querySelector("#confirm").addEventListener("click", () => {
+        const tdsElements = document.querySelectorAll("#params .params-name");
+        const tds = Array.from(tdsElements);
+
+        const items = Array.from(startValues).map((item, index) => {
+          const endAt = tds.findIndex((td) => td === item);
+
+          return {
+            label: item.innerHTML,
+            startAt: index,
+            endAt: endAt
+          };
+        });
+
+        vscode.postMessage({
+          values: JSON.stringify(items)
+        });
+      });
+    </script>
+  </body>
+</html>
+  `;
+  return html;
+}
diff --git a/src/editor/code-reference.ts b/src/editor/code-reference.ts
new file mode 100644
index 00000000..c045eab1
--- /dev/null
+++ b/src/editor/code-reference.ts
@@ -0,0 +1,9 @@
+import { Selection } from "./selection";
+import { Path } from "./path";
+
+export class CodeReference {
+  constructor(
+    public readonly path: Path,
+    public readonly selection: Selection
+  ) {}
+}
diff --git a/src/editor/editor.ts b/src/editor/editor.ts
index 3768a5e4..d44bbf49 100644
--- a/src/editor/editor.ts
+++ b/src/editor/editor.ts
@@ -1,18 +1,19 @@
 import { Selection } from "./selection";
-import { RelativePath } from "./path";
+import { Path } from "./path";
 import { Position } from "./position";
 import { ErrorReason } from "./error-reason";
+import { CodeReference } from "./code-reference";
 
 export { AbsolutePath, RelativePath } from "./path";
 export { ErrorReason, toString as errorReasonToString } from "./error-reason";
 
 export interface Editor {
-  workspaceFiles(): Promise<RelativePath[]>;
+  workspaceFiles(): Promise<Path[]>;
   readonly selection: Selection;
   readonly code: Code;
-  codeOf(path: RelativePath): Promise<Code>;
+  codeOf(path: Path): Promise<Code>;
   write(code: Code, newCursorPosition?: Position): Promise<void>;
-  writeIn(path: RelativePath, code: Code): Promise<void>;
+  writeIn(path: Path, code: Code): Promise<void>;
   readThenWrite(
     selection: Selection,
     getModifications: (code: Code) => Modification[],
@@ -26,6 +27,11 @@ export interface Editor {
     placeHolder?: string
   ): Promise<Choice<T> | undefined>;
   moveCursorTo(position: Position): Promise<void>;
+  getSelectionReferences(selection: Selection): Promise<CodeReference[]>;
+  askForPositions(
+    params: SelectedPosition[],
+    callback: (positions: SelectedPosition[]) => Promise<void>
+  ): Promise<void>;
 }
 
 export type Modification = {
@@ -50,3 +56,11 @@ export type Choice<T> = {
   description?: string;
   icon?: "file-code";
 };
+
+export type SelectedPosition = Omit<
+  Choice<{
+    startAt: number;
+    endAt: number;
+  }>,
+  "description" | "icon"
+>;
diff --git a/src/editor/error-reason.ts b/src/editor/error-reason.ts
index 8118a966..3c7ffa5f 100644
--- a/src/editor/error-reason.ts
+++ b/src/editor/error-reason.ts
@@ -1,4 +1,5 @@
 export enum ErrorReason {
+  CantChangeSignatureException,
   DidNotFindClass,
   DidNotFindObjectToDestructure,
   DidNotFindNumericLiteral,
@@ -59,6 +60,9 @@ export enum ErrorReason {
 
 export function toString(reason: ErrorReason): string {
   switch (reason) {
+    case ErrorReason.CantChangeSignatureException:
+      return cantDoIt("change function signature");
+
     case ErrorReason.DidNotFindClass:
       return didNotFind("a class to create a factory for");
 
diff --git a/src/editor/path.ts b/src/editor/path.ts
index c982745e..c1754cd7 100644
--- a/src/editor/path.ts
+++ b/src/editor/path.ts
@@ -38,6 +38,10 @@ export class Path {
     const relativeValue = path.relative(path.dirname(value), this.value);
     return new RelativePath(relativeValue);
   }
+
+  absoluteFrom(value: string): AbsolutePath {
+    return new AbsolutePath(value);
+  }
 }
 
 export class AbsolutePath extends Path {
@@ -48,6 +52,10 @@ export class AbsolutePath extends Path {
       throw new Error(`${value} is not an absolute path`);
     }
   }
+
+  absoluteFrom(_value: string): AbsolutePath {
+    return this;
+  }
 }
 
 export class RelativePath extends Path {
diff --git a/src/extension.ts b/src/extension.ts
index 7810b5bd..734ea784 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -5,6 +5,7 @@ import { RefactoringActionProvider } from "./action-providers";
 import { Refactoring, RefactoringWithActionProvider } from "./types";
 
 import addNumericSeparator from "./refactorings/add-numeric-separator";
+import changeSignature from "./refactorings/change-signature";
 import convertForToForEach from "./refactorings/convert-for-to-for-each";
 import convertForEachToForOf from "./refactorings/convert-for-each-to-for-of";
 import convertIfElseToSwitch from "./refactorings/convert-if-else-to-switch";
@@ -83,6 +84,7 @@ const refactorings: { [key: string]: ConfiguredRefactoring } = {
     ],
     withActionProvider: [
       addNumericSeparator,
+      changeSignature,
       convertForEachToForOf,
       convertForToForEach,
       convertIfElseToSwitch,
diff --git a/src/playground/maths/calculator.ts b/src/playground/maths/calculator.ts
new file mode 100644
index 00000000..19fb18e9
--- /dev/null
+++ b/src/playground/maths/calculator.ts
@@ -0,0 +1,11 @@
+import { add } from "./maths";
+
+add(3, 400);
+
+// Used to view what params generates in webview table
+function viewParametersTable(
+  literal: number | string | boolean,
+  obj = {},
+  [desc, descTwo, ...rest]: any[],
+  { item, itemTwo, ...anotherRest }: Record<string, any>
+) {}
diff --git a/src/playground/maths/maths.ts b/src/playground/maths/maths.ts
new file mode 100644
index 00000000..eb2d00b8
--- /dev/null
+++ b/src/playground/maths/maths.ts
@@ -0,0 +1,5 @@
+export function add(param: number, num2: number) {
+  return param + num2;
+}
+
+add(1, 200);
diff --git a/src/refactorings/change-signature/change-signature.ts b/src/refactorings/change-signature/change-signature.ts
new file mode 100644
index 00000000..e99c6ad7
--- /dev/null
+++ b/src/refactorings/change-signature/change-signature.ts
@@ -0,0 +1,197 @@
+import { Editor, ErrorReason, SelectedPosition } from "../../editor/editor";
+import { Selection } from "../../editor/selection";
+import * as t from "../../ast";
+import { Path } from "../../editor/path";
+
+let toModifyNode: t.FunctionDeclaration;
+
+export async function changeSignature(editor: Editor) {
+  const params =
+    toModifyNode?.params.map((p, index) => {
+      return {
+        label: getParamName(p),
+        value: {
+          startAt: index,
+          endAt: index
+        }
+      };
+    }) || [];
+
+  await editor.askForPositions(params, async (newPositions) => {
+    const { selection } = editor;
+    const refrences = await editor.getSelectionReferences(selection);
+
+    const filesContent = await Promise.all(
+      refrences.map(async (reference) => {
+        const content = await editor.codeOf(reference.path);
+        return {
+          code: content,
+          path: reference.path,
+          selection: reference.selection
+        };
+      })
+    );
+
+    const alreadyTransformed: Record<string, string> = {};
+    const result: {
+      path: Path;
+      transformed: t.Transformed;
+    }[] = [];
+
+    for (const x of filesContent) {
+      const codeToTransform =
+        alreadyTransformed[x.path.value] || (x.code as string);
+
+      try {
+        const transformed = updateCode(
+          t.parse(codeToTransform),
+          x.selection,
+          newPositions
+        );
+
+        alreadyTransformed[x.path.value] = `${transformed.code}`;
+
+        result.push({
+          path: x.path,
+          transformed
+        });
+      } catch (error) {
+        editor.showError(ErrorReason.CantChangeSignatureException);
+        console.log(error);
+        return;
+      }
+    }
+
+    await Promise.all(
+      result.map(async (result) => {
+        await editor.writeIn(
+          result.path,
+          alreadyTransformed[result.path.value]
+        );
+      })
+    );
+  });
+}
+
+function updateCode(
+  ast: t.AST,
+  selection: Selection,
+  newPositions: SelectedPosition[]
+): t.Transformed {
+  return t.transformAST(
+    ast,
+    createVisitorForReferences(selection, (path) => {
+      const node = path.node;
+
+      if (t.isCallExpression(node)) {
+        const args = node.arguments.slice();
+        if (args.length) {
+          newPositions.forEach((order) => {
+            const arg = node.arguments[order.value.startAt];
+            args[order.value.endAt] = arg;
+          });
+        }
+
+        const newArgs = args.map((arg) => {
+          if (arg) return arg;
+
+          return t.identifier("undefined");
+        });
+        node.arguments = newArgs;
+      } else if (t.isFunctionDeclaration(node)) {
+        const params = node.params.slice();
+        if (params.length) {
+          newPositions.forEach((order) => {
+            const arg = node.params[order.value.startAt];
+            params[order.value.endAt] = arg;
+          });
+        }
+
+        node.params = params;
+      }
+
+      path.stop();
+    })
+  );
+}
+
+export function createVisitor(
+  selection: Selection,
+  onMatch: (path: t.NodePath) => void
+): t.Visitor {
+  return {
+    FunctionDeclaration(path) {
+      if (!selection.isInsidePath(path)) return;
+
+      toModifyNode = path.node;
+      onMatch(path);
+    }
+  };
+}
+
+function getParamName(
+  param:
+    | t.Identifier
+    | t.Pattern
+    | t.RestElement
+    | t.LVal
+    | t.ObjectProperty
+    | t.PatternLike
+    | t.Expression
+    | t.PrivateName
+): string {
+  if ("name" in param) {
+    return param.name;
+  }
+
+  if (t.isRestElement(param)) {
+    return `...${getParamName(param.argument)}`;
+  }
+
+  if (t.isObjectPattern(param)) {
+    const names: string[] = param.properties.map((property) => {
+      if ("key" in property) return getParamName(property.key);
+
+      return getParamName(property);
+    });
+    // For object destructuring put {param} as name
+    return `{${names.join(", ")}}`;
+  }
+
+  if (t.isAssignmentPattern(param)) {
+    return getParamName(param.left);
+  }
+
+  if (t.isArrayPattern(param)) {
+    const names: string[] = param.elements.map((element) => {
+      return getParamName(element as t.PatternLike);
+    });
+
+    // For array destructuring put [param] as name
+    return `[${names.join(", ")}]`;
+  }
+
+  return "unknown";
+}
+
+function createVisitorForReferences(
+  selection: Selection,
+  onMatch: (path: t.NodePath) => void
+): t.Visitor {
+  return {
+    CallExpression(path) {
+      const nodeSelection = new Selection(
+        [path.node.loc?.start.line || 0, 0],
+        [path.node.loc?.end.line || 0, 0]
+      );
+
+      if (!selection.isSameLineThan(nodeSelection)) return;
+
+      onMatch(path);
+    },
+    FunctionDeclaration(path) {
+      if (!selection.isInsidePath(path)) return;
+      onMatch(path);
+    }
+  };
+}
diff --git a/src/refactorings/change-signature/index.ts b/src/refactorings/change-signature/index.ts
new file mode 100644
index 00000000..211c41c3
--- /dev/null
+++ b/src/refactorings/change-signature/index.ts
@@ -0,0 +1,17 @@
+import { changeSignature, createVisitor } from "./change-signature";
+
+import { RefactoringWithActionProvider } from "../../types";
+
+const config: RefactoringWithActionProvider = {
+  command: {
+    key: "changeSignature",
+    operation: changeSignature,
+    title: "Change Signature"
+  },
+  actionProvider: {
+    message: "Change signature",
+    createVisitor
+  }
+};
+
+export default config;


Test patch:
diff --git a/src/playground/playground.ts b/src/playground/playground.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/refactorings/change-signature/change-signature.test.ts b/src/refactorings/change-signature/change-signature.test.ts
new file mode 100644
index 00000000..75be3a91
--- /dev/null
+++ b/src/refactorings/change-signature/change-signature.test.ts
@@ -0,0 +1,363 @@
+import { Code, ErrorReason, AbsolutePath } from "../../editor/editor";
+import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
+import { testEach } from "../../tests-helpers";
+
+import { changeSignature } from "./change-signature";
+
+type TestSample = {
+  code: Code;
+  path: AbsolutePath;
+};
+
+describe("Change Signature", () => {
+  testEach<{
+    code: Code;
+    expected: Code;
+  }>(
+    "In same file",
+    [
+      {
+        description: "when there are a function without references",
+        code: `function [cursor]add(a, b) {
+            return a + b;
+          }`,
+        expected: `function add(b, a) {
+            return a + b;
+          }`
+      },
+      {
+        description: "when there are a defined function with references",
+        code: `function [cursor]add(a, b) {
+            return a + b;
+          }
+
+          add(1, 2);`,
+        expected: `function add(b, a) {
+            return a + b;
+          }
+
+          add(2, 1);`
+      },
+      {
+        description:
+          "only wanted function keeping contract of the rest of functions",
+        code: `function [cursor]add(a, b) {
+            return a + b;
+          }
+
+          add(2, 3);
+
+          function subtract(a, b) {
+            return a - b;
+          }
+
+          subtract(1, 2);
+          `,
+        expected: `function add(b, a) {
+            return a + b;
+          }
+
+          add(3, 2);
+
+          function subtract(a, b) {
+            return a - b;
+          }
+
+          subtract(1, 2);
+          `
+      },
+      {
+        description:
+          "when there are a defined function with multiples references",
+        code: `function [cursor]add(a, b) {
+            return a + b;
+          }
+
+          add(1, 2);
+          add(3, 4);
+          add(5, 6);
+          add(7, 8);`,
+        expected: `function add(b, a) {
+            return a + b;
+          }
+
+          add(2, 1);
+          add(4, 3);
+          add(6, 5);
+          add(8, 7);`
+      },
+      {
+        description:
+          "when there are a defined function with multiple references in a conditions",
+        code: `function [cursor]add(a, b) {
+            return a + b;
+          }
+
+          if(add(1, 2)) {
+            switch(add(3, 4)) {
+              case 7:
+                console.log('Inside');
+            };
+          }
+          add(7, 8);`,
+        expected: `function add(b, a) {
+            return a + b;
+          }
+
+          if(add(2, 1)) {
+            switch(add(4, 3)) {
+              case 7:
+                console.log('Inside');
+            };
+          }
+          add(8, 7);`
+      },
+      {
+        description: "when has a destructuring param",
+        code: `function [cursor]add(a, {item}) {
+            return a + item;
+          }
+
+          add(7, {item: 1});`,
+        expected: `function add({item}, a) {
+            return a + item;
+          }
+
+          add({item: 1}, 7);`
+      },
+      {
+        description: "with types in a ts code",
+        code: `function [cursor]add(a: number, str: string) {
+            return a + item;
+          }
+
+          add(7, " years");`,
+        expected: `function add(str: string, a: number) {
+            return a + item;
+          }
+
+          add(" years", 7);`
+      },
+      {
+        description: "with default values in parameters",
+        code: `function [cursor]add(a = 1, str = "Adios") {
+            return a + str;
+          }
+
+          add(7, " years");
+          add(1);`,
+        expected: `function add(str = "Adios", a = 1) {
+            return a + str;
+          }
+
+          add(" years", 7);
+          add(undefined, 1);`
+      },
+      {
+        description: "in a ts code with default parameters and types",
+        code: `function [cursor]add(a: number = 1, str: string = "Adios") {
+            return a + item;
+          }
+
+          add(7, " years");
+          add();
+          add(1);`,
+        expected: `function add(str: string = "Adios", a: number = 1) {
+            return a + item;
+          }
+
+          add(" years", 7);
+          add();
+          add(undefined, 1);`
+      }
+    ],
+    async ({ code, expected }) => {
+      const path = new AbsolutePath("/temp/aFile.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);
+    }
+  );
+
+  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
+              }
+            ]
+          }
+        }
+      ],
+      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 oldLog = console.log;
+    console.log = () => {};
+    const code = `
+    function [cursor]aFn(a, ...args) {
+      return args.push(a);
+    }
+
+    aFn(0, 1);
+    `;
+    const editor = new InMemoryEditor(code);
+    await editor.writeIn(new AbsolutePath("/temp/aModule.js"), editor.code);
+    jest.spyOn(editor, "showError");
+    editor.saveUserChoices(userChangePositionOf(0, 1));
+    editor.saveUserChoices(userChangePositionOf(1, 0));
+
+    await changeSignature(editor);
+
+    expect(editor.showError).toBeCalledWith(
+      ErrorReason.CantChangeSignatureException
+    );
+    console.log = oldLog;
+  });
+
+  it("Should order correclty for complex parameters with defaults values", async () => {
+    const { setup, expected } = {
+      setup: {
+        currentFile: `function [cursor]add(a, str, {item}, [value = 1]) {
+            console.log(a, str, item, value, args)
+          }
+
+          add(7, " years", {item: 3}, [1]);`,
+        path: new AbsolutePath("/temp/aFileWithReferencesInsideSameFile.ts")
+      },
+      expected: {
+        currentFile: `function add([value = 1], {item}, str, a) {
+            console.log(a, str, item, value, args)
+          }
+
+          add([1], {item: 3}, " years", 7);`
+      }
+    };
+
+    const editor = new InMemoryEditor(setup.currentFile);
+    await editor.writeIn(setup.path, editor.code);
+    editor.saveUserChoices(userChangePositionOf(0, 3));
+    editor.saveUserChoices(userChangePositionOf(3, 0));
+    editor.saveUserChoices(userChangePositionOf(1, 2));
+    editor.saveUserChoices(userChangePositionOf(2, 1));
+
+    await changeSignature(editor);
+
+    const extracted = await editor.codeOf(setup.path);
+    expect(extracted).toBe(expected.currentFile);
+  });
+});
+
+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) {
+  return {
+    label: "irrelevant",
+    value: {
+      startAt,
+      endAt
+    }
+  };
+}