Repository: nicoespeon/abracadabra
Pull request #511 (2022-02-14 15:47:45)

Change the export convention
## Context

We used to put the exported symbols at the top of each file. The idea was that it would be easier to see what's exported from each file at a glance.

However, that doesn't work well with types since [we started using babel for running tests](https://github.com/nicoespeon/abracadabra/blob/main/docs/adr/0011-use-babel-jest-instead-of-ts-jest.md). That created inconsistencies with some symbols being exported at the top of the file, and types being exported where they are declared.

That got me to reflect on exporting symbols at the top of the file. While the idea was to clarify what's the public API of each file, it's _not_ the most frequent question I have when working with the code. TypeScript is already providing auto-completion and auto-imports the symbols we need to use. However, having the exports far from the declaration made it harder to answer the question: "is this symbol exported?".

Consistency and being able to tell if the symbol we are looking at is exported outweigh the simplicity of seeing what a given file exports.

## Decision

The `export` keyword is used on the declaration of the symbol, as much as possible

## Consequences

Before:

```ts
export { doThis, doThat };

function doThis() {}
function doThat() {}
```

Now:

```ts
export function doThis() {}
export function doThat() {}
```

The pros:

- Easier to tell if a given symbol is exported
- No more inconsistency with types that have to be exported where they are declared
- No more inconsistency when we want to export a `const` (they had to be declared _before_ the export)

The cons:

- Harder to tell what is being exported from a file. That's fine because TS helps with auto-import/complete. Also, folding everything make it quite easy to detect the exported symbols.



Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 64f9971a..9c4e0ef8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -121,17 +121,16 @@ import { parse } from "@babel/parser";
 import { Selection } from "./selection";
 import { Position } from "./position";
 
-// 3. Exports
-export { isStringLiteral, isClassDeclaration };
-export { StringLiteral };
-
-// 4. Rest of the code
-function isStringLiteral() {
-  // …
+// 3. Rest of the code, starting with high-level concepts
+export function statementWithBraces(node: t.Statement): t.Statement {
+  return isBlockStatement(node) ? node : blockStatement([node]);
 }
+
+function isBlockStatement() {}
+function blockStatement() {}
 ```
 
-As a general rule, we prefer to have **what is exposed appear before what is private**. That's why we list the exports at the top of the file. We find it simpler to see what is exposed from a file, so it's easier to decide if that's too much and we should split.
+As a general rule, we prefer to have **what is exposed appear before what is private**. We like to read the high-level concepts before the implementation details.
 
 ## Debug locally
 
diff --git a/_templates/refactoring/new/refactoring.ejs.t b/_templates/refactoring/new/refactoring.ejs.t
index 321d049d..f5b8a8b1 100644
--- a/_templates/refactoring/new/refactoring.ejs.t
+++ b/_templates/refactoring/new/refactoring.ejs.t
@@ -9,13 +9,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-<% if (hasActionProvider){ -%>
-export { <%= camelName %>, createVisitor };
-<% } else { -%>
-export { <%= camelName %> };
-<% } -%>
-
-async function <%= camelName %>(editor: Editor) {
+export async function <%= camelName %>(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -36,7 +30,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath) => void
 ): t.Visitor {
diff --git a/docs/adr/0012-export-symbols-at-declaration.md b/docs/adr/0012-export-symbols-at-declaration.md
new file mode 100644
index 00000000..5c7c2cb3
--- /dev/null
+++ b/docs/adr/0012-export-symbols-at-declaration.md
@@ -0,0 +1,49 @@
+# 12. Export symbols at declaration
+
+Date: 2022-02-14
+
+## Status
+
+Accepted
+
+## Context
+
+We used to put the exported symbols at the top of each file. The idea was that it would be easier to see what's exported from each file at a glance.
+
+However, that doesn't work well with types since [we started using babel for running tests](./0011-use-babel-jest-instead-of-ts-jest.md). That created inconsistencies with some symbols being exported at the top of the file, and types being exported where they are declared.
+
+That got me to reflect on exporting symbols at the top of the file. While the idea was to clarify what's the public API of each file, it's _not_ the most frequent question I have when working with the code. TypeScript is already providing auto-completion and auto-imports the symbols we need to use. However, having the exports far from the declaration made it harder to answer the question: "is this symbol exported?".
+
+Consistency and being able to tell if the symbol we are looking at is exported outweigh the simplicity of seeing what a given file exports.
+
+## Decision
+
+The `export` keyword is used on the declaration of the symbol, as much as possible
+
+## Consequences
+
+Before:
+
+```ts
+export { doThis, doThat };
+
+function doThis() {}
+function doThat() {}
+```
+
+Now:
+
+```ts
+export function doThis() {}
+export function doThat() {}
+```
+
+The pros:
+
+- Easier to tell if a given symbol is exported
+- No more inconsistency with types that have to be exported where they are declared
+- No more inconsistency when we want to export a `const` (they had to be declared _before_ the export)
+
+The cons:
+
+- Harder to tell what is being exported from a file. That's fine because TS helps with auto-import/complete. Also, folding everything make it quite easy to detect the exported symbols.
diff --git a/src/action-providers.ts b/src/action-providers.ts
index 0f3200a0..63e9f329 100644
--- a/src/action-providers.ts
+++ b/src/action-providers.ts
@@ -12,11 +12,9 @@ import {
 } from "./vscode-configuration";
 import { TypeChecker, ConsoleLogger } from "./type-checker";
 
-export { RefactoringActionProvider };
-
 type Refactoring = RefactoringWithActionProvider;
 
-class RefactoringActionProvider implements vscode.CodeActionProvider {
+export class RefactoringActionProvider implements vscode.CodeActionProvider {
   constructor(private refactorings: Refactoring[]) {}
 
   provideCodeActions(
diff --git a/src/array.ts b/src/array.ts
index 1a65a93e..c0a2c9fb 100644
--- a/src/array.ts
+++ b/src/array.ts
@@ -1,17 +1,15 @@
-export { first, last, allButLast, isLast };
-
-function first<T>(array: T[] | ReadonlyArray<T>): T | undefined {
+export function first<T>(array: T[] | ReadonlyArray<T>): T | undefined {
   return array[0];
 }
 
-function last<T>(array: T[] | ReadonlyArray<T>): T | undefined {
+export function last<T>(array: T[] | ReadonlyArray<T>): T | undefined {
   return array[array.length - 1];
 }
 
-function allButLast<T>(array: T[]): T[] {
+export function allButLast<T>(array: T[]): T[] {
   return array.slice(0, -1);
 }
 
-function isLast<T>(array: Array<T>, index: number): boolean {
+export function isLast<T>(array: Array<T>, index: number): boolean {
   return array.length - 1 === index;
 }
diff --git a/src/ast/domain.ts b/src/ast/domain.ts
index 475ed8cb..1c8cef01 100644
--- a/src/ast/domain.ts
+++ b/src/ast/domain.ts
@@ -2,24 +2,8 @@ import { NodePath } from "@babel/traverse";
 import * as t from "@babel/types";
 
 export * from "@babel/types";
-export {
-  addImportDeclaration,
-  getImportDeclarations,
-  getReturnedStatement,
-  getAssignedStatement,
-  getStatements,
-  getNodesBelow,
-  getPathsBelow,
-  isEmpty,
-  isLet,
-  replaceWithBodyOf,
-  forEach,
-  statementWithBraces,
-  statementWithoutBraces,
-  toArrowFunctionExpression
-};
-
-function addImportDeclaration(
+
+export function addImportDeclaration(
   programPath: NodePath<t.Program>,
   identifier: t.Identifier,
   sourcePath: string
@@ -42,7 +26,7 @@ function addImportDeclaration(
   programPath.node.body.unshift(importStatement);
 }
 
-function getImportDeclarations(
+export function getImportDeclarations(
   programPath: NodePath<t.Program>
 ): t.ImportDeclaration[] {
   return programPath.node.body.filter(
@@ -51,7 +35,7 @@ function getImportDeclarations(
   );
 }
 
-function getReturnedStatement(
+export function getReturnedStatement(
   node: t.Statement | null
 ): t.ReturnStatement | null {
   if (!t.isBlockStatement(node)) return null;
@@ -62,7 +46,7 @@ function getReturnedStatement(
   return firstChild;
 }
 
-function getAssignedStatement(
+export function getAssignedStatement(
   node: t.Statement | null
 ): (t.ExpressionStatement & { expression: t.AssignmentExpression }) | null {
   if (!t.isBlockStatement(node)) return null;
@@ -77,21 +61,23 @@ function getAssignedStatement(
   return { ...firstChild, expression };
 }
 
-function getStatements(statement: t.Statement): t.Statement[] {
+export function getStatements(statement: t.Statement): t.Statement[] {
   return t.isBlockStatement(statement) ? statement.body : [statement];
 }
 
-function getNodesBelow(path: NodePath<t.IfStatement>): t.Statement[] {
+export function getNodesBelow(path: NodePath<t.IfStatement>): t.Statement[] {
   return getPathsBelow(path).map((path) => path.node);
 }
 
-function getPathsBelow(path: NodePath<t.IfStatement>): NodePath<t.Statement>[] {
+export function getPathsBelow(
+  path: NodePath<t.IfStatement>
+): NodePath<t.Statement>[] {
   return path
     .getAllNextSiblings()
     .filter((path): path is NodePath<t.Statement> => t.isStatement(path));
 }
 
-function isEmpty(statement: t.Statement): boolean {
+export function isEmpty(statement: t.Statement): boolean {
   const statements = getStatements(statement).filter(
     (child) => child !== statement
   );
@@ -99,11 +85,11 @@ function isEmpty(statement: t.Statement): boolean {
   return statements.length === 0;
 }
 
-function isLet(node: t.Node): node is t.VariableDeclaration {
+export function isLet(node: t.Node): node is t.VariableDeclaration {
   return "kind" in node && node.kind === "let";
 }
 
-function replaceWithBodyOf(path: NodePath, node: t.Statement) {
+export function replaceWithBodyOf(path: NodePath, node: t.Statement) {
   path.replaceWithMultiple(getStatements(node));
 }
 
@@ -117,7 +103,7 @@ export type TypeDeclaration =
   | t.TSTypeAliasDeclaration
   | t.TSInterfaceDeclaration;
 
-function forEach(
+export function forEach(
   object: t.Expression,
   params: t.ArrowFunctionExpression["params"],
   body: t.BlockStatement
@@ -129,15 +115,15 @@ function forEach(
   );
 }
 
-function statementWithBraces(node: t.Statement): t.Statement {
+export function statementWithBraces(node: t.Statement): t.Statement {
   return t.isBlockStatement(node) ? node : t.blockStatement([node]);
 }
 
-function statementWithoutBraces(node: t.Statement): t.Statement {
+export function statementWithoutBraces(node: t.Statement): t.Statement {
   return t.isBlockStatement(node) ? node.body[0] : node;
 }
 
-function toArrowFunctionExpression({
+export function toArrowFunctionExpression({
   node
 }: NodePath<t.FunctionDeclaration | t.FunctionExpression>) {
   const arrowFunctionExpression = t.arrowFunctionExpression(
diff --git a/src/ast/export-default-workaround.ts b/src/ast/export-default-workaround.ts
index 32cbef8d..51eaae0a 100644
--- a/src/ast/export-default-workaround.ts
+++ b/src/ast/export-default-workaround.ts
@@ -4,8 +4,6 @@ import { NodePath } from "@babel/traverse";
 import { isSelectablePath } from "./selection";
 import { Selection } from "../editor/selection";
 
-export { getExportDefaultDeclarationLoc };
-
 /**
  * For some reason, default export declarations have no LOC.
  * That's likely a bug in the parser that would be solved at some point.
@@ -13,7 +11,7 @@ export { getExportDefaultDeclarationLoc };
  * Until then, we use `getExportDefaultDeclarationLoc` to work around this.
  */
 
-function getExportDefaultDeclarationLoc(
+export function getExportDefaultDeclarationLoc(
   path: NodePath<t.ExportDefaultDeclaration>
 ): t.SourceLocation | null {
   if (!isSelectablePath(path)) return null;
diff --git a/src/ast/identity.ts b/src/ast/identity.ts
index eb96bd10..b7a2394e 100644
--- a/src/ast/identity.ts
+++ b/src/ast/identity.ts
@@ -5,37 +5,7 @@ import { last } from "../array";
 import { Selection } from "../editor/selection";
 import { isSelectableNode, SelectablePath } from "./selection";
 
-export {
-  isClassPropertyIdentifier,
-  isVariableDeclarationIdentifier,
-  isFunctionDeclarationOrArrowFunction,
-  isFunctionCallIdentifier,
-  isJSXPartialElement,
-  isPropertyOfMemberExpression,
-  isArrayExpressionElement,
-  areAllObjectProperties,
-  isUndefinedLiteral,
-  isGuardClause,
-  isGuardConsequentBlock,
-  isNonEmptyReturn,
-  hasFinalReturn,
-  hasBraces,
-  hasAlternate,
-  hasSingleStatementBlock,
-  isTruthy,
-  isFalsy,
-  areSameAssignments,
-  areEquivalent,
-  isTemplateExpression,
-  isInBranchedLogic,
-  isInAlternate,
-  areOpposite,
-  areOppositeOperators,
-  getOppositeOperator,
-  canBeShorthand
-};
-
-function isClassPropertyIdentifier(path: NodePath): boolean {
+export function isClassPropertyIdentifier(path: NodePath): boolean {
   return (
     t.isClassProperty(path.parent) &&
     !path.parent.computed &&
@@ -43,25 +13,25 @@ function isClassPropertyIdentifier(path: NodePath): boolean {
   );
 }
 
-function isVariableDeclarationIdentifier(path: NodePath): boolean {
+export function isVariableDeclarationIdentifier(path: NodePath): boolean {
   return t.isVariableDeclarator(path.parent) && t.isIdentifier(path);
 }
 
-function isFunctionDeclarationOrArrowFunction(
+export function isFunctionDeclarationOrArrowFunction(
   node: t.Node
 ): node is t.FunctionDeclaration | t.ArrowFunctionExpression {
   return t.isFunctionDeclaration(node) || t.isArrowFunctionExpression(node);
 }
 
-function isFunctionCallIdentifier(path: NodePath): boolean {
+export function isFunctionCallIdentifier(path: NodePath): boolean {
   return t.isCallExpression(path.parent) && path.parent.callee === path.node;
 }
 
-function isJSXPartialElement(path: NodePath): boolean {
+export function isJSXPartialElement(path: NodePath): boolean {
   return t.isJSXOpeningElement(path) || t.isJSXClosingElement(path);
 }
 
-function isPropertyOfMemberExpression(path: NodePath): boolean {
+export function isPropertyOfMemberExpression(path: NodePath): boolean {
   return (
     (t.isMemberExpression(path.parent) ||
       t.isOptionalMemberExpression(path.parent)) &&
@@ -70,26 +40,26 @@ function isPropertyOfMemberExpression(path: NodePath): boolean {
   );
 }
 
-function isArrayExpressionElement(
+export function isArrayExpressionElement(
   node: t.Node | null
 ): node is null | t.Expression | t.SpreadElement {
   return node === null || t.isExpression(node) || t.isSpreadElement(node);
 }
 
-function areAllObjectProperties(
+export function areAllObjectProperties(
   nodes: (t.Node | null)[]
 ): nodes is t.ObjectProperty[] {
   return nodes.every((node) => t.isObjectProperty(node));
 }
 
-function isUndefinedLiteral(
+export function isUndefinedLiteral(
   node: object | null | undefined,
   opts?: object | null
 ): node is t.Identifier {
   return t.isIdentifier(node, opts) && node.name === "undefined";
 }
 
-function isGuardClause(path: NodePath<t.IfStatement>) {
+export function isGuardClause(path: NodePath<t.IfStatement>) {
   const { consequent, alternate } = path.node;
   // eslint-disable-next-line no-extra-boolean-cast
   if (Boolean(alternate)) return false;
@@ -97,29 +67,29 @@ function isGuardClause(path: NodePath<t.IfStatement>) {
   return t.isReturnStatement(consequent) || isGuardConsequentBlock(consequent);
 }
 
-function isTruthy(test: t.Expression): boolean {
+export function isTruthy(test: t.Expression): boolean {
   return areEquivalent(test, t.booleanLiteral(true));
 }
 
-function isFalsy(test: t.Expression): boolean {
+export function isFalsy(test: t.Expression): boolean {
   return areEquivalent(test, t.booleanLiteral(false));
 }
 
-function isGuardConsequentBlock(
+export function isGuardConsequentBlock(
   consequent: t.IfStatement["consequent"]
 ): consequent is t.BlockStatement {
   return t.isBlockStatement(consequent) && hasFinalReturn(consequent.body);
 }
 
-function isNonEmptyReturn(node: t.Node) {
+export function isNonEmptyReturn(node: t.Node) {
   return t.isReturnStatement(node) && node.argument !== null;
 }
 
-function hasFinalReturn(statements: t.Statement[]): boolean {
+export function hasFinalReturn(statements: t.Statement[]): boolean {
   return t.isReturnStatement(last(statements));
 }
 
-function hasBraces(
+export function hasBraces(
   path: SelectablePath<t.IfStatement>,
   selection: Selection
 ): boolean {
@@ -138,15 +108,17 @@ function hasBraces(
   }
 }
 
-export type IfStatementWithAlternate = t.IfStatement & { alternate: t.Statement };
+export type IfStatementWithAlternate = t.IfStatement & {
+  alternate: t.Statement;
+};
 
-function hasAlternate(
+export function hasAlternate(
   path: NodePath<t.IfStatement>
 ): path is NodePath<IfStatementWithAlternate> {
   return Boolean(path.node.alternate);
 }
 
-function hasSingleStatementBlock(
+export function hasSingleStatementBlock(
   path: NodePath<t.IfStatement>,
   selection: Selection
 ): boolean {
@@ -165,7 +137,7 @@ function hasSingleStatementBlock(
   }
 }
 
-function areSameAssignments(
+export function areSameAssignments(
   expressionA: t.AssignmentExpression,
   expressionB: t.AssignmentExpression
 ): boolean {
@@ -175,7 +147,7 @@ function areSameAssignments(
   );
 }
 
-function areEquivalent(
+export function areEquivalent(
   nodeA: t.Node | null | undefined,
   nodeB: t.Node | null | undefined
 ): boolean {
@@ -327,7 +299,7 @@ function areAllEqual(
   );
 }
 
-function isTemplateExpression(node: t.Node): node is TemplateExpression {
+export function isTemplateExpression(node: t.Node): node is TemplateExpression {
   return (
     t.isIdentifier(node) ||
     t.isCallExpression(node) ||
@@ -337,11 +309,11 @@ function isTemplateExpression(node: t.Node): node is TemplateExpression {
 
 type TemplateExpression = t.Identifier | t.CallExpression | t.MemberExpression;
 
-function isInBranchedLogic(path: NodePath<t.ReturnStatement>) {
+export function isInBranchedLogic(path: NodePath<t.ReturnStatement>) {
   return path.getAncestry().some((path) => t.isIfStatement(path));
 }
 
-function isInAlternate(path: NodePath<t.IfStatement>): boolean {
+export function isInAlternate(path: NodePath<t.IfStatement>): boolean {
   const { parentPath } = path;
 
   return t.isBlockStatement(parentPath)
@@ -351,7 +323,7 @@ function isInAlternate(path: NodePath<t.IfStatement>): boolean {
         parentPath.node.alternate === path.node;
 }
 
-function areOpposite(testA: t.Expression, testB: t.Expression): boolean {
+export function areOpposite(testA: t.Expression, testB: t.Expression): boolean {
   if (!t.isBinaryExpression(testA)) return false;
   if (!t.isBinaryExpression(testB)) return false;
 
@@ -385,7 +357,7 @@ const OPPOSITE_OPERATORS: t.BinaryExpression["operator"][][] = [
   [">=", "<"]
 ];
 
-function areOppositeOperators(
+export function areOppositeOperators(
   operatorA: t.BinaryExpression["operator"],
   operatorB: t.BinaryExpression["operator"]
 ): boolean {
@@ -396,7 +368,7 @@ function areOppositeOperators(
   );
 }
 
-function getOppositeOperator(
+export function getOppositeOperator(
   operator: t.BinaryExpression["operator"]
 ): t.BinaryExpression["operator"] {
   let result: t.BinaryExpression["operator"] | undefined;
@@ -409,7 +381,9 @@ function getOppositeOperator(
   return result || operator;
 }
 
-function canBeShorthand(path: NodePath): path is NodePath<t.ObjectProperty> {
+export function canBeShorthand(
+  path: NodePath
+): path is NodePath<t.ObjectProperty> {
   return (
     t.isObjectProperty(path.node) &&
     !path.node.computed &&
diff --git a/src/ast/scope.ts b/src/ast/scope.ts
index b9b8a43b..4920915a 100644
--- a/src/ast/scope.ts
+++ b/src/ast/scope.ts
@@ -9,23 +9,7 @@ import {
 } from "./identity";
 import { isSelectablePath, SelectablePath } from "./selection";
 
-export {
-  findScopePath,
-  findParentIfPath,
-  getFunctionScopePath,
-  isShadowIn,
-  findFirstExistingDeclaration,
-  findCommonAncestorToDeclareVariable,
-  findAncestorThatCanHaveVariableDeclaration,
-  bindingNamesInScope,
-  referencesInScope,
-  getReferencedImportDeclarations,
-  getTypeReferencedImportDeclarations,
-  hasReferencesDefinedInSameScope,
-  hasTypeReferencesDefinedInSameScope
-};
-
-function findScopePath(path: NodePath<t.Node | null>): NodePath | null {
+export function findScopePath(path: NodePath<t.Node | null>): NodePath | null {
   return path.findParent(
     (parentPath) =>
       t.isExpressionStatement(parentPath) ||
@@ -44,7 +28,7 @@ function findScopePath(path: NodePath<t.Node | null>): NodePath | null {
   );
 }
 
-function findParentIfPath(
+export function findParentIfPath(
   path: NodePath<t.Node | null>
 ): NodePath<t.IfStatement> | undefined {
   return path.findParent((parentPath) => t.isIfStatement(parentPath)) as
@@ -52,11 +36,13 @@ function findParentIfPath(
     | undefined;
 }
 
-function getFunctionScopePath(path: NodePath<t.FunctionDeclaration>): NodePath {
+export function getFunctionScopePath(
+  path: NodePath<t.FunctionDeclaration>
+): NodePath {
   return path.getFunctionParent() || path.parentPath;
 }
 
-function isShadowIn(
+export function isShadowIn(
   id: t.Identifier,
   ancestors: t.TraversalAncestors
 ): boolean {
@@ -90,7 +76,9 @@ function isShadowIn(
   }
 }
 
-function findFirstExistingDeclaration(expressionPath: NodePath<t.Expression>) {
+export function findFirstExistingDeclaration(
+  expressionPath: NodePath<t.Expression>
+) {
   const existingDeclarations: NodePath<DestructuredVariableDeclarator>[] =
     Object.values(expressionPath.scope.getAllBindings())
       .map(({ path }) => path as NodePath)
@@ -110,7 +98,7 @@ type DestructuredVariableDeclarator = t.VariableDeclarator & {
   init: t.Identifier;
 };
 
-function findCommonAncestorToDeclareVariable(
+export function findCommonAncestorToDeclareVariable(
   path: NodePath,
   otherPaths: NodePath[]
 ): SelectablePath | null {
@@ -134,7 +122,7 @@ function getProgramPath(path: NodePath): NodePath {
   return allAncestors[allAncestors.length - 1];
 }
 
-function findAncestorThatCanHaveVariableDeclaration<T extends t.Node>(
+export function findAncestorThatCanHaveVariableDeclaration<T extends t.Node>(
   path: NodePath<T> | null
 ): SelectablePath<T> | null {
   if (path === null) return null;
@@ -147,11 +135,11 @@ function findAncestorThatCanHaveVariableDeclaration<T extends t.Node>(
   return findAncestorThatCanHaveVariableDeclaration(path.parentPath);
 }
 
-function bindingNamesInScope<T>(path: NodePath<T>): string[] {
+export function bindingNamesInScope<T>(path: NodePath<T>): string[] {
   return Object.keys(path.scope.getAllBindings());
 }
 
-function referencesInScope(
+export function referencesInScope(
   path: NodePath<t.FunctionDeclaration | t.FunctionExpression>
 ): NodePath[] {
   const allBindings = Object.values(path.scope.getAllBindings()) as Binding[];
@@ -164,7 +152,7 @@ function referencesInScope(
   );
 }
 
-function getReferencedImportDeclarations(
+export function getReferencedImportDeclarations(
   functionPath: NodePath<t.FunctionDeclaration>,
   programPath: NodePath<t.Program>
 ): t.ImportDeclaration[] {
@@ -193,7 +181,7 @@ function getReferencedImportDeclarations(
   return result;
 }
 
-function getTypeReferencedImportDeclarations(
+export function getTypeReferencedImportDeclarations(
   typePath: NodePath<TypeDeclaration>,
   programPath: NodePath<t.Program>
 ): t.ImportDeclaration[] {
@@ -222,7 +210,7 @@ function getTypeReferencedImportDeclarations(
   return result;
 }
 
-function hasReferencesDefinedInSameScope(
+export function hasReferencesDefinedInSameScope(
   functionPath: NodePath<t.FunctionDeclaration>,
   programPath: NodePath<t.Program>
 ): boolean {
@@ -252,7 +240,7 @@ function hasReferencesDefinedInSameScope(
   return result;
 }
 
-function hasTypeReferencesDefinedInSameScope(
+export function hasTypeReferencesDefinedInSameScope(
   typePath: NodePath<TypeDeclaration>
 ): boolean {
   let result = false;
diff --git a/src/ast/selection.ts b/src/ast/selection.ts
index ee5f2f83..d9d4efc0 100644
--- a/src/ast/selection.ts
+++ b/src/ast/selection.ts
@@ -1,14 +1,6 @@
 import { NodePath } from "@babel/traverse";
 import * as t from "@babel/types";
 
-export {
-  isSelectablePath,
-  isSelectableNode,
-  isSelectableVariableDeclarator,
-  isSelectableIdentifier,
-  isSelectableObjectProperty
-};
-
 export interface ASTSelection {
   start: ASTPosition;
   end: ASTPosition;
@@ -31,29 +23,29 @@ export interface SelectableObjectProperty extends t.ObjectProperty {
   value: Selectable<t.ObjectProperty["value"]>;
 }
 
-function isSelectablePath<T extends t.Node>(
+export function isSelectablePath<T extends t.Node>(
   path: NodePath<T>
 ): path is SelectablePath<T> {
   return !!path.node.loc;
 }
 
-function isSelectableNode(node: t.Node | null): node is SelectableNode {
+export function isSelectableNode(node: t.Node | null): node is SelectableNode {
   return !!node && !!node.loc;
 }
 
-function isSelectableIdentifier(
+export function isSelectableIdentifier(
   node: t.Node | null
 ): node is SelectableIdentifier {
   return t.isIdentifier(node) && isSelectableNode(node);
 }
 
-function isSelectableVariableDeclarator(
+export function isSelectableVariableDeclarator(
   declaration: t.VariableDeclarator
 ): declaration is SelectableVariableDeclarator {
   return !!declaration.loc;
 }
 
-function isSelectableObjectProperty(
+export function isSelectableObjectProperty(
   property: t.Node | null
 ): property is SelectableObjectProperty {
   return (
diff --git a/src/ast/siblings.ts b/src/ast/siblings.ts
index 63f8dde6..f9b09b86 100644
--- a/src/ast/siblings.ts
+++ b/src/ast/siblings.ts
@@ -1,23 +1,15 @@
 import { NodePath } from "@babel/traverse";
 import * as t from "@babel/types";
 
-export {
-  getPreviousSibling,
-  getNextSibling,
-  hasSiblingStatement,
-  getPreviousSiblingStatements,
-  getNextSiblingStatements
-};
-
-function getPreviousSibling(path: NodePath): NodePath | undefined {
+export function getPreviousSibling(path: NodePath): NodePath | undefined {
   return path.getAllPrevSiblings()[0];
 }
 
-function getNextSibling(path: NodePath): NodePath | undefined {
+export function getNextSibling(path: NodePath): NodePath | undefined {
   return path.getAllNextSiblings()[0];
 }
 
-function hasSiblingStatement(path: NodePath): boolean {
+export function hasSiblingStatement(path: NodePath): boolean {
   const allSiblingStatements = [
     ...getPreviousSiblingStatements(path),
     ...getNextSiblingStatements(path)
@@ -26,14 +18,14 @@ function hasSiblingStatement(path: NodePath): boolean {
   return allSiblingStatements.length > 0;
 }
 
-function getPreviousSiblingStatements(path: NodePath): t.Statement[] {
+export function getPreviousSiblingStatements(path: NodePath): t.Statement[] {
   return path
     .getAllPrevSiblings()
     .map(({ node }) => node)
     .filter(isStatement);
 }
 
-function getNextSiblingStatements(path: NodePath): t.Statement[] {
+export function getNextSiblingStatements(path: NodePath): t.Statement[] {
   return path
     .getAllNextSiblings()
     .map(({ node }) => node)
diff --git a/src/ast/switch.ts b/src/ast/switch.ts
index f398993d..3d5a52f7 100644
--- a/src/ast/switch.ts
+++ b/src/ast/switch.ts
@@ -1,10 +1,8 @@
 import * as t from "@babel/types";
 
-export { toSwitch, VALID_OPERATORS };
+export const VALID_OPERATORS: t.BinaryExpression["operator"][] = ["==", "==="];
 
-const VALID_OPERATORS: t.BinaryExpression["operator"][] = ["==", "==="];
-
-function toSwitch(expression: t.Expression): Switch | null {
+export function toSwitch(expression: t.Expression): Switch | null {
   if (!t.isBinaryExpression(expression)) return null;
   if (!VALID_OPERATORS.includes(expression.operator)) return null;
 
diff --git a/src/ast/template-literal.ts b/src/ast/template-literal.ts
index 674f571b..7eef9f86 100644
--- a/src/ast/template-literal.ts
+++ b/src/ast/template-literal.ts
@@ -1,20 +1,18 @@
 import { NodePath } from "@babel/traverse";
 import * as t from "@babel/types";
 
-export { templateElement, convertStringToTemplateLiteral };
-
 /**
  * Override babel `templateElement()` because it exposes
  * unnecessary implementation details and it's not type-safe.
  */
-function templateElement(value: string): t.TemplateElement {
+export function templateElement(value: string): t.TemplateElement {
   return t.templateElement({
     raw: value,
     cooked: value
   });
 }
 
-function convertStringToTemplateLiteral(
+export function convertStringToTemplateLiteral(
   path: NodePath<t.StringLiteral>,
   loc: t.SourceLocation
 ): t.TemplateLiteral {
diff --git a/src/ast/transformation.ts b/src/ast/transformation.ts
index a36016f0..c476a594 100644
--- a/src/ast/transformation.ts
+++ b/src/ast/transformation.ts
@@ -1,42 +1,22 @@
 import { parse as babelParse } from "@babel/parser";
-import traverse, {
-  TraverseOptions,
-  NodePath,
-  Visitor,
-  Binding
-} from "@babel/traverse";
+import traverse, { TraverseOptions, NodePath, Visitor } from "@babel/traverse";
 import * as t from "@babel/types";
 import * as recast from "recast";
 
 import { Code } from "../editor/editor";
 import { findScopePath } from "./scope";
 
-const traverseNode = t.traverse;
-const traversePath = traverse;
-
-export { NodePath, Scope } from "@babel/traverse";
-export { isRootNodePath };
-export {
-  traverseNode,
-  traversePath,
-  traverseAST,
-  parseAndTraverseCode,
-  parse,
-  transform,
-  transformAST,
-  transformCopy,
-  print,
-  isUsingTabs,
-  Binding
-};
-export { mergeCommentsInto };
+export const traverseNode = t.traverse;
+export const traversePath = traverse;
+
+export { NodePath, Scope, Binding } from "@babel/traverse";
 export type { Visitor };
 
-function transform(code: Code, options: TraverseOptions): Transformed {
+export function transform(code: Code, options: TraverseOptions): Transformed {
   return transformAST(parse(code), options);
 }
 
-function transformAST(ast: AST, options: TraverseOptions): Transformed {
+export function transformAST(ast: AST, options: TraverseOptions): Transformed {
   const code = print(ast);
   const newCode = fixShebang(print(traverseAST(ast, options)));
 
@@ -54,7 +34,7 @@ function fixShebang(newCode: Code): Code {
   return shebang ? newCode.replace(shebang, `${shebang}\n\n`) : newCode;
 }
 
-function isUsingTabs(ast: AST | t.Node): boolean {
+export function isUsingTabs(ast: AST | t.Node): boolean {
   let useTabs = false;
 
   try {
@@ -89,7 +69,7 @@ function indentWithTabs(code: Code): Code {
     .join("\n");
 }
 
-function print(ast: AST | t.Node): Code {
+export function print(ast: AST | t.Node): Code {
   return recast.print(ast, {
     lineTerminator: "\n"
   }).code;
@@ -99,11 +79,11 @@ function standardizeEOL(code: Code): Code {
   return code.replace(/\r/g, "");
 }
 
-function parseAndTraverseCode(code: Code, opts: TraverseOptions): AST {
+export function parseAndTraverseCode(code: Code, opts: TraverseOptions): AST {
   return traverseAST(parse(code), opts);
 }
 
-function parse(code: Code): AST {
+export function parse(code: Code): AST {
   try {
     return recast.parse(code, {
       parser: {
@@ -167,7 +147,7 @@ function parse(code: Code): AST {
   }
 }
 
-function traverseAST(ast: AST, opts: TraverseOptions): AST {
+export function traverseAST(ast: AST, opts: TraverseOptions): AST {
   traverse(ast, opts);
   return ast;
 }
@@ -182,7 +162,7 @@ function traverseAST(ast: AST, opts: TraverseOptions): AST {
  * It's temporary though.
  * After we're done, we remove the inserted path. #magicTrick ✨
  */
-function transformCopy<T extends t.Node>(
+export function transformCopy<T extends t.Node>(
   path: NodePath,
   node: T,
   traverseOptions: Visitor
@@ -255,7 +235,7 @@ export interface Transformed {
 
 export type AST = t.File;
 
-function mergeCommentsInto<T extends t.Node>(
+export function mergeCommentsInto<T extends t.Node>(
   node: T,
   commentedNodes: t.Node[]
 ): T {
@@ -273,7 +253,7 @@ export type RootNodePath<T = t.Node> = NodePath<T> & {
   parentPath: NodePath<t.Program>;
 };
 
-function isRootNodePath<T = t.Node>(
+export function isRootNodePath<T = t.Node>(
   path: NodePath<T>
 ): path is RootNodePath<T> {
   return path.parentPath?.isProgram() ?? false;
diff --git a/src/commands.ts b/src/commands.ts
index 4a371f46..5577eeb9 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -3,9 +3,7 @@ import * as vscode from "vscode";
 import { Operation } from "./types";
 import { createVSCodeEditor } from "./editor/adapters/create-vscode-editor";
 
-export { createCommand, executeSafely };
-
-function createCommand(execute: Operation) {
+export function createCommand(execute: Operation) {
   return async () => {
     const editor = createVSCodeEditor();
     if (!editor) return;
@@ -14,7 +12,9 @@ function createCommand(execute: Operation) {
   };
 }
 
-async function executeSafely(command: () => Promise<any>): Promise<void> {
+export async function executeSafely(
+  command: () => Promise<any>
+): Promise<void> {
   try {
     await command();
   } catch (error) {
diff --git a/src/editor/adapters/attempting-editor.ts b/src/editor/adapters/attempting-editor.ts
index 1eb3af0e..c97b0cd9 100644
--- a/src/editor/adapters/attempting-editor.ts
+++ b/src/editor/adapters/attempting-editor.ts
@@ -10,9 +10,7 @@ import {
 import { Position } from "../position";
 import { Selection } from "../selection";
 
-export { AttemptingEditor };
-
-class AttemptingEditor implements Editor {
+export class AttemptingEditor implements Editor {
   attemptSucceeded = true;
 
   constructor(private editor: Editor, private expectedReason: ErrorReason) {}
diff --git a/src/editor/adapters/create-vscode-editor.ts b/src/editor/adapters/create-vscode-editor.ts
index 661b3f61..7b688f34 100644
--- a/src/editor/adapters/create-vscode-editor.ts
+++ b/src/editor/adapters/create-vscode-editor.ts
@@ -3,9 +3,7 @@ import * as vscode from "vscode";
 import { VSCodeEditor } from "./vscode-editor";
 import { VueVSCodeEditor } from "./vue-vscode-editor";
 
-export { createVSCodeEditor };
-
-function createVSCodeEditor(): VSCodeEditor | undefined {
+export function createVSCodeEditor(): VSCodeEditor | undefined {
   const activeTextEditor = vscode.window.activeTextEditor;
   if (!activeTextEditor) return;
 
diff --git a/src/editor/adapters/in-memory-editor.ts b/src/editor/adapters/in-memory-editor.ts
index 6ff6c671..5d774896 100644
--- a/src/editor/adapters/in-memory-editor.ts
+++ b/src/editor/adapters/in-memory-editor.ts
@@ -11,8 +11,6 @@ import {
 import { Selection } from "../selection";
 import { Position } from "../position";
 
-export { InMemoryEditor };
-
 const LINE_SEPARATOR = "\n";
 const CHARS_SEPARATOR = "";
 const DELETED_LINE = "___DELETED_LINE___";
@@ -20,7 +18,7 @@ const CURSOR = "[cursor]";
 const SELECTION_START = "[start]";
 const SELECTION_END = "[end]";
 
-class InMemoryEditor implements Editor {
+export class InMemoryEditor implements Editor {
   private codeMatrix: CodeMatrix = [];
   private _selection: Selection = Selection.cursorAt(0, 0);
   private otherFiles = new Map<RelativePath, Editor>();
diff --git a/src/editor/adapters/vscode-editor.ts b/src/editor/adapters/vscode-editor.ts
index fa89c7bd..c8c63259 100644
--- a/src/editor/adapters/vscode-editor.ts
+++ b/src/editor/adapters/vscode-editor.ts
@@ -15,9 +15,7 @@ import { Selection } from "../selection";
 import { Position } from "../position";
 import { AbsolutePath, RelativePath } from "../path";
 
-export { VSCodeEditor };
-
-class VSCodeEditor implements Editor {
+export class VSCodeEditor implements Editor {
   private editor: vscode.TextEditor;
   private document: vscode.TextDocument;
 
diff --git a/src/editor/adapters/vue-vscode-editor.ts b/src/editor/adapters/vue-vscode-editor.ts
index 59667750..18c9db5d 100644
--- a/src/editor/adapters/vue-vscode-editor.ts
+++ b/src/editor/adapters/vue-vscode-editor.ts
@@ -5,9 +5,7 @@ import { Position } from "../position";
 import { Selection } from "../selection";
 import { VSCodeEditor } from "./vscode-editor";
 
-export { VueVSCodeEditor };
-
-class VueVSCodeEditor extends VSCodeEditor {
+export class VueVSCodeEditor extends VSCodeEditor {
   get code(): Code {
     return super.code.slice(this.openingTagOffset, this.closingTagOffset);
   }
diff --git a/src/editor/editor.ts b/src/editor/editor.ts
index be021f24..3768a5e4 100644
--- a/src/editor/editor.ts
+++ b/src/editor/editor.ts
@@ -1,14 +1,10 @@
 import { Selection } from "./selection";
-import { AbsolutePath, RelativePath } from "./path";
+import { RelativePath } from "./path";
 import { Position } from "./position";
-import { ErrorReason, toString } from "./error-reason";
+import { ErrorReason } from "./error-reason";
 
-export {
-  AbsolutePath,
-  RelativePath,
-  ErrorReason,
-  toString as errorReasonToString
-};
+export { AbsolutePath, RelativePath } from "./path";
+export { ErrorReason, toString as errorReasonToString } from "./error-reason";
 
 export interface Editor {
   workspaceFiles(): Promise<RelativePath[]>;
diff --git a/src/editor/path.ts b/src/editor/path.ts
index 0691f046..c982745e 100644
--- a/src/editor/path.ts
+++ b/src/editor/path.ts
@@ -1,9 +1,7 @@
 import * as _path from "path";
 const path = _path.posix;
 
-export { Path, AbsolutePath, RelativePath };
-
-class Path {
+export class Path {
   constructor(protected _value: string) {}
 
   get value(): string {
@@ -42,7 +40,7 @@ class Path {
   }
 }
 
-class AbsolutePath extends Path {
+export class AbsolutePath extends Path {
   constructor(value: string) {
     super(value);
 
@@ -52,7 +50,7 @@ class AbsolutePath extends Path {
   }
 }
 
-class RelativePath extends Path {
+export class RelativePath extends Path {
   constructor(value: string) {
     super(value);
 
diff --git a/src/editor/position.ts b/src/editor/position.ts
index 0cd5f83b..eae8f2d3 100644
--- a/src/editor/position.ts
+++ b/src/editor/position.ts
@@ -2,9 +2,7 @@ import { last } from "../array";
 import { ASTPosition, NodePath, isSelectableNode } from "../ast";
 import { Code } from "./editor";
 
-export { Position };
-
-class Position {
+export class Position {
   private _line: number;
   private _character: number;
 
diff --git a/src/editor/selection.ts b/src/editor/selection.ts
index d2069c11..2851a588 100644
--- a/src/editor/selection.ts
+++ b/src/editor/selection.ts
@@ -2,9 +2,7 @@ import { Position } from "./position";
 import * as t from "../ast";
 import { ASTSelection } from "../ast";
 
-export { Selection };
-
-class Selection {
+export class Selection {
   private _start: Position;
   private _end: Position;
 
diff --git a/src/refactorings/add-numeric-separator/add-numeric-separator.ts b/src/refactorings/add-numeric-separator/add-numeric-separator.ts
index d86496b4..09d2dc2d 100644
--- a/src/refactorings/add-numeric-separator/add-numeric-separator.ts
+++ b/src/refactorings/add-numeric-separator/add-numeric-separator.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { addNumericSeparator, createVisitor };
-
-async function addNumericSeparator(editor: Editor) {
+export async function addNumericSeparator(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -41,7 +39,7 @@ function addSeparators(value: number): string {
   return floatingPart ? `${result}.${floatingPart}` : result;
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.NumericLiteral>) => void
 ): t.Visitor {
diff --git a/src/refactorings/convert-for-each-to-for-of/convert-for-each-to-for-of.ts b/src/refactorings/convert-for-each-to-for-of/convert-for-each-to-for-of.ts
index f3c662a7..6f9601a8 100644
--- a/src/refactorings/convert-for-each-to-for-of/convert-for-each-to-for-of.ts
+++ b/src/refactorings/convert-for-each-to-for-of/convert-for-each-to-for-of.ts
@@ -4,9 +4,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Position } from "../../editor/position";
 import { Selection } from "../../editor/selection";
 
-export { convertForEachToForOf, createVisitor };
-
-async function convertForEachToForOf(editor: Editor) {
+export async function convertForEachToForOf(editor: Editor) {
   const { code, selection } = editor;
   const { transformed: updatedCode, forEachStartLine } = updateCode(
     t.parse(code),
@@ -69,7 +67,7 @@ function updateCode(
   return { transformed, forEachStartLine };
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.SelectablePath<t.CallExpression>,
diff --git a/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.ts b/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.ts
index 1b1848f5..0c4619bb 100644
--- a/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.ts
+++ b/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.ts
@@ -6,9 +6,7 @@ import * as t from "../../ast";
 import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
 import { Position } from "../../editor/position";
 
-export { convertForToForEach, createVisitor as canConvertForLoop };
-
-async function convertForToForEach(editor: Editor) {
+export async function convertForToForEach(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -50,7 +48,7 @@ function updateCode(
   return { ...result, forLoopStartLine };
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.SelectablePath<t.ForStatement | t.ForOfStatement>,
diff --git a/src/refactorings/convert-for-to-for-each/index.ts b/src/refactorings/convert-for-to-for-each/index.ts
index 5723c0cf..8f9d4862 100644
--- a/src/refactorings/convert-for-to-for-each/index.ts
+++ b/src/refactorings/convert-for-to-for-each/index.ts
@@ -1,7 +1,4 @@
-import {
-  canConvertForLoop,
-  convertForToForEach
-} from "./convert-for-to-for-each";
+import { createVisitor, convertForToForEach } from "./convert-for-to-for-each";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -13,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert to forEach",
-    createVisitor: canConvertForLoop,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/convert-if-else-to-switch/convert-if-else-to-switch.ts b/src/refactorings/convert-if-else-to-switch/convert-if-else-to-switch.ts
index 32d9b852..3dc16ee2 100644
--- a/src/refactorings/convert-if-else-to-switch/convert-if-else-to-switch.ts
+++ b/src/refactorings/convert-if-else-to-switch/convert-if-else-to-switch.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { convertIfElseToSwitch, createVisitor as hasIfElseToConvert };
-
-async function convertIfElseToSwitch(editor: Editor) {
+export async function convertIfElseToSwitch(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -26,7 +24,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.IfStatement>,
diff --git a/src/refactorings/convert-if-else-to-switch/index.ts b/src/refactorings/convert-if-else-to-switch/index.ts
index caeca243..c033bbdf 100644
--- a/src/refactorings/convert-if-else-to-switch/index.ts
+++ b/src/refactorings/convert-if-else-to-switch/index.ts
@@ -1,5 +1,5 @@
 import {
-  hasIfElseToConvert,
+  createVisitor,
   convertIfElseToSwitch
 } from "./convert-if-else-to-switch";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert if/else to switch",
-    createVisitor: hasIfElseToConvert,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/convert-if-else-to-ternary/convert-if-else-to-ternary.ts b/src/refactorings/convert-if-else-to-ternary/convert-if-else-to-ternary.ts
index 2d3d0a93..cb149859 100644
--- a/src/refactorings/convert-if-else-to-ternary/convert-if-else-to-ternary.ts
+++ b/src/refactorings/convert-if-else-to-ternary/convert-if-else-to-ternary.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { convertIfElseToTernary, createVisitor as hasIfElseToConvert };
-
-async function convertIfElseToTernary(editor: Editor) {
+export async function convertIfElseToTernary(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -26,7 +24,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.IfStatement>, node: t.Node) => void
 ): t.Visitor {
diff --git a/src/refactorings/convert-if-else-to-ternary/index.ts b/src/refactorings/convert-if-else-to-ternary/index.ts
index 089fadf7..66fa6620 100644
--- a/src/refactorings/convert-if-else-to-ternary/index.ts
+++ b/src/refactorings/convert-if-else-to-ternary/index.ts
@@ -1,5 +1,5 @@
 import {
-  hasIfElseToConvert,
+  createVisitor,
   convertIfElseToTernary
 } from "./convert-if-else-to-ternary";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert if/else to ternary",
-    createVisitor: hasIfElseToConvert
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/convert-let-to-const/convert-let-to-const.ts b/src/refactorings/convert-let-to-const/convert-let-to-const.ts
index a031028d..7530cc85 100644
--- a/src/refactorings/convert-let-to-const/convert-let-to-const.ts
+++ b/src/refactorings/convert-let-to-const/convert-let-to-const.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { convertLetToConst, createVisitor };
-
-async function convertLetToConst(editor: Editor) {
+export async function convertLetToConst(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -26,7 +24,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.VariableDeclarator>,
diff --git a/src/refactorings/convert-switch-to-if-else/convert-switch-to-if-else.ts b/src/refactorings/convert-switch-to-if-else/convert-switch-to-if-else.ts
index b0f5d05c..6d8cef3f 100644
--- a/src/refactorings/convert-switch-to-if-else/convert-switch-to-if-else.ts
+++ b/src/refactorings/convert-switch-to-if-else/convert-switch-to-if-else.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 import { last, allButLast } from "../../array";
 
-export { convertSwitchToIfElse, createVisitor as hasSwitchToConvert };
-
-async function convertSwitchToIfElse(editor: Editor) {
+export async function convertSwitchToIfElse(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -31,7 +29,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.SwitchStatement>,
diff --git a/src/refactorings/convert-switch-to-if-else/index.ts b/src/refactorings/convert-switch-to-if-else/index.ts
index 920f7de5..5521d03e 100644
--- a/src/refactorings/convert-switch-to-if-else/index.ts
+++ b/src/refactorings/convert-switch-to-if-else/index.ts
@@ -1,6 +1,6 @@
 import {
   convertSwitchToIfElse,
-  hasSwitchToConvert
+  createVisitor
 } from "./convert-switch-to-if-else";
 
 import { RefactoringWithActionProvider } from "../../types";
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert switch to if/else",
-    createVisitor: hasSwitchToConvert,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/convert-ternary-to-if-else/convert-ternary-to-if-else.ts b/src/refactorings/convert-ternary-to-if-else/convert-ternary-to-if-else.ts
index 5237083c..ff9c541f 100644
--- a/src/refactorings/convert-ternary-to-if-else/convert-ternary-to-if-else.ts
+++ b/src/refactorings/convert-ternary-to-if-else/convert-ternary-to-if-else.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { convertTernaryToIfElse, createVisitor as hasTernaryToConvert };
-
-async function convertTernaryToIfElse(editor: Editor) {
+export async function convertTernaryToIfElse(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -91,7 +89,7 @@ function updateCode(
   return { ...result, otherVariablesDeclared };
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.ConditionalExpression>) => void
 ): t.Visitor {
diff --git a/src/refactorings/convert-ternary-to-if-else/index.ts b/src/refactorings/convert-ternary-to-if-else/index.ts
index ca110bcc..171eeea7 100644
--- a/src/refactorings/convert-ternary-to-if-else/index.ts
+++ b/src/refactorings/convert-ternary-to-if-else/index.ts
@@ -1,5 +1,5 @@
 import {
-  hasTernaryToConvert,
+  createVisitor,
   convertTernaryToIfElse
 } from "./convert-ternary-to-if-else";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert ternary to if/else",
-    createVisitor: hasTernaryToConvert
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/convert-to-arrow-function/convert-to-arrow-function.ts b/src/refactorings/convert-to-arrow-function/convert-to-arrow-function.ts
index d0a54143..79ee2675 100644
--- a/src/refactorings/convert-to-arrow-function/convert-to-arrow-function.ts
+++ b/src/refactorings/convert-to-arrow-function/convert-to-arrow-function.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { convertToArrowFunction, createVisitor };
-
-async function convertToArrowFunction(editor: Editor) {
+export async function convertToArrowFunction(editor: Editor) {
   const { code, selection } = editor;
   const { updatedCode, hasReferenceBefore } = updateCode(
     t.parse(code),
@@ -103,7 +101,7 @@ class FunctionExpressionConverter implements Converter {
   }
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.FunctionDeclaration | t.FunctionExpression>
diff --git a/src/refactorings/convert-to-template-literal/convert-to-template-literal.ts b/src/refactorings/convert-to-template-literal/convert-to-template-literal.ts
index 8d2328df..b6d9cfcf 100644
--- a/src/refactorings/convert-to-template-literal/convert-to-template-literal.ts
+++ b/src/refactorings/convert-to-template-literal/convert-to-template-literal.ts
@@ -2,12 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  convertToTemplateLiteral,
-  createVisitor as canConvertToTemplateLiteral
-};
-
-async function convertToTemplateLiteral(editor: Editor) {
+export async function convertToTemplateLiteral(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -40,7 +35,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.StringLiteral>) => void
 ): t.Visitor {
diff --git a/src/refactorings/convert-to-template-literal/index.ts b/src/refactorings/convert-to-template-literal/index.ts
index b51dc3eb..1ff45280 100644
--- a/src/refactorings/convert-to-template-literal/index.ts
+++ b/src/refactorings/convert-to-template-literal/index.ts
@@ -1,5 +1,5 @@
 import {
-  canConvertToTemplateLiteral,
+  createVisitor,
   convertToTemplateLiteral
 } from "./convert-to-template-literal";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Convert to template literal",
-    createVisitor: canConvertToTemplateLiteral,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/create-factory-for-constructor/create-factory-for-constructor.ts b/src/refactorings/create-factory-for-constructor/create-factory-for-constructor.ts
index eec6ef85..0463049a 100644
--- a/src/refactorings/create-factory-for-constructor/create-factory-for-constructor.ts
+++ b/src/refactorings/create-factory-for-constructor/create-factory-for-constructor.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { createFactoryForConstructor, createVisitor };
-
-async function createFactoryForConstructor(editor: Editor) {
+export async function createFactoryForConstructor(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -139,7 +137,7 @@ function toSpreadElement(rest: t.RestElement): t.SpreadElement {
   return t.spreadElement(argument);
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.ClassDeclaration>) => void
 ): t.Visitor {
diff --git a/src/refactorings/destructure-object/destructure-object.ts b/src/refactorings/destructure-object/destructure-object.ts
index 92b08ceb..be63ae8b 100644
--- a/src/refactorings/destructure-object/destructure-object.ts
+++ b/src/refactorings/destructure-object/destructure-object.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import { TypeChecker, ConsoleLogger } from "../../type-checker";
 import * as t from "../../ast";
 
-export { destructureObject, createVisitor };
-
-async function destructureObject(editor: Editor) {
+export async function destructureObject(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(code, selection);
 
@@ -66,7 +64,7 @@ function updateCode(code: Code, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.Identifier>, keys: string[]) => void,
   typeChecker: TypeChecker
diff --git a/src/refactorings/extract-class/extract-class.ts b/src/refactorings/extract-class/extract-class.ts
index 2f1cd40a..e438fabc 100644
--- a/src/refactorings/extract-class/extract-class.ts
+++ b/src/refactorings/extract-class/extract-class.ts
@@ -2,11 +2,9 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { extractClass, createVisitor };
-
 // REFACTOR: this refactoring wasn't implemented following the usual pattern. See https://github.com/nicoespeon/abracadabra/issues/180
 
-async function extractClass(editor: Editor) {
+export async function extractClass(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -27,7 +25,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   _selection: Selection,
   _onMatch: (path: t.NodePath) => void
 ): t.Visitor {
diff --git a/src/refactorings/extract-generic-type/extract-generic-type.ts b/src/refactorings/extract-generic-type/extract-generic-type.ts
index c61ccc5d..ff34ab70 100644
--- a/src/refactorings/extract-generic-type/extract-generic-type.ts
+++ b/src/refactorings/extract-generic-type/extract-generic-type.ts
@@ -9,9 +9,7 @@ import {
 import { renameSymbol } from "../rename-symbol/rename-symbol";
 import { last } from "../../array";
 
-export { extractGenericType, createVisitor, Occurrence };
-
-async function extractGenericType(editor: Editor) {
+export async function extractGenericType(editor: Editor) {
   const { code, selection } = editor;
   const ast = t.parse(code);
 
@@ -68,7 +66,7 @@ interface AllOccurrences {
   others: Occurrence[];
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (occurrence: Occurrence) => void,
   onVisit: (occurrence: Occurrence) => void = () => {}
@@ -117,7 +115,7 @@ function findParentFunctionDeclaration(
   return declaration ? new FunctionDeclaration(declaration) : null;
 }
 
-class Occurrence {
+export class Occurrence {
   readonly symbolPosition?: Position;
   protected readonly typeName: string;
 
diff --git a/src/refactorings/extract-interface/extract-interface.ts b/src/refactorings/extract-interface/extract-interface.ts
index 599746f4..34b9431c 100644
--- a/src/refactorings/extract-interface/extract-interface.ts
+++ b/src/refactorings/extract-interface/extract-interface.ts
@@ -4,9 +4,7 @@ import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 import { renameSymbol } from "../rename-symbol/rename-symbol";
 
-export { extractInterface, createVisitor as canExtractInterface };
-
-async function extractInterface(editor: Editor) {
+export async function extractInterface(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -63,7 +61,7 @@ function updateCode(
   return { ...result, interfaceIdentifierPosition };
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.ClassDeclaration>,
diff --git a/src/refactorings/extract-interface/index.ts b/src/refactorings/extract-interface/index.ts
index f79a8f57..b78ccc85 100644
--- a/src/refactorings/extract-interface/index.ts
+++ b/src/refactorings/extract-interface/index.ts
@@ -1,4 +1,4 @@
-import { extractInterface, canExtractInterface } from "./extract-interface";
+import { extractInterface, createVisitor } from "./extract-interface";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Extract interface",
-    createVisitor: canExtractInterface
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/extract/extract-type/extract-type.ts b/src/refactorings/extract/extract-type/extract-type.ts
index bcd50dee..05bd78fd 100644
--- a/src/refactorings/extract/extract-type/extract-type.ts
+++ b/src/refactorings/extract/extract-type/extract-type.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../../editor/selection";
 import { Position } from "../../../editor/position";
 import * as t from "../../../ast";
 
-export { extractType };
-
-async function extractType(editor: Editor) {
+export async function extractType(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
diff --git a/src/refactorings/extract/extract-variable/extract-variable.ts b/src/refactorings/extract/extract-variable/extract-variable.ts
index 59dadae7..e7831b0c 100644
--- a/src/refactorings/extract/extract-variable/extract-variable.ts
+++ b/src/refactorings/extract/extract-variable/extract-variable.ts
@@ -9,9 +9,7 @@ import {
   askReplacementStrategy
 } from "../replacement-strategy";
 
-export { extractVariable };
-
-async function extractVariable(editor: Editor) {
+export async function extractVariable(editor: Editor) {
   const { code, selection } = editor;
   const { selected: selectedOccurrence, others: otherOccurrences } =
     findAllOccurrences(code, selection);
diff --git a/src/refactorings/extract/extract-variable/occurrence.ts b/src/refactorings/extract/extract-variable/occurrence.ts
index 7ab9a0ce..b082099d 100644
--- a/src/refactorings/extract/extract-variable/occurrence.ts
+++ b/src/refactorings/extract/extract-variable/occurrence.ts
@@ -18,9 +18,7 @@ import {
 } from "./variable-declaration-modification";
 import { last } from "../../../array";
 
-export { createOccurrence, Occurrence };
-
-function createOccurrence(
+export function createOccurrence(
   path: t.NodePath,
   loc: t.SourceLocation,
   selection: Selection
@@ -106,7 +104,7 @@ function createOccurrence(
   return new Occurrence(path, loc, new Variable(path));
 }
 
-class Occurrence<T extends t.Node = t.Node> {
+export class Occurrence<T extends t.Node = t.Node> {
   constructor(
     public path: t.NodePath<T>,
     public loc: t.SourceLocation,
diff --git a/src/refactorings/extract/extract-variable/parts.ts b/src/refactorings/extract/extract-variable/parts.ts
index 89fd962e..557d59f3 100644
--- a/src/refactorings/extract/extract-variable/parts.ts
+++ b/src/refactorings/extract/extract-variable/parts.ts
@@ -2,9 +2,7 @@ import { Code } from "../../../editor/editor";
 import { Selection } from "../../../editor/selection";
 import { Position } from "../../../editor/position";
 
-export { Parts };
-
-class Parts {
+export class Parts {
   constructor(
     private readonly code: Code,
     private readonly selection: Selection,
diff --git a/src/refactorings/extract/extract-variable/variable-declaration-modification.ts b/src/refactorings/extract/extract-variable/variable-declaration-modification.ts
index c036fed7..943721e4 100644
--- a/src/refactorings/extract/extract-variable/variable-declaration-modification.ts
+++ b/src/refactorings/extract/extract-variable/variable-declaration-modification.ts
@@ -6,13 +6,7 @@ import { assert } from "../../../assert";
 
 import { Occurrence } from "./occurrence";
 
-export {
-  VariableDeclarationModification,
-  DeclarationOnCommonAncestor,
-  MergeDestructuredDeclaration
-};
-
-class VariableDeclarationModification implements Modification {
+export class VariableDeclarationModification implements Modification {
   constructor(
     private name: string,
     private value: string,
@@ -33,7 +27,7 @@ class VariableDeclarationModification implements Modification {
   }
 }
 
-class DeclarationOnCommonAncestor extends VariableDeclarationModification {
+export class DeclarationOnCommonAncestor extends VariableDeclarationModification {
   constructor(
     name: string,
     value: string,
@@ -73,7 +67,7 @@ function topToBottom(a: Occurrence, b: Occurrence): number {
   return a.selection.startsBefore(b.selection) ? -1 : 1;
 }
 
-class MergeDestructuredDeclaration implements Modification {
+export class MergeDestructuredDeclaration implements Modification {
   constructor(
     private name: string,
     private lastDestructuredProperty: t.SelectableNode
diff --git a/src/refactorings/extract/extract-variable/variable.ts b/src/refactorings/extract/extract-variable/variable.ts
index a1e69b6f..338d82ee 100644
--- a/src/refactorings/extract/extract-variable/variable.ts
+++ b/src/refactorings/extract/extract-variable/variable.ts
@@ -3,14 +3,7 @@ import { camelCase } from "change-case";
 import { Code } from "../../../editor/editor";
 import * as t from "../../../ast";
 
-export {
-  Variable,
-  StringLiteralVariable,
-  MemberExpressionVariable,
-  ShorthandVariable
-};
-
-class Variable<T = t.Node> {
+export class Variable<T = t.Node> {
   protected _name: string;
 
   constructor(protected path: t.NodePath<T>) {
@@ -83,7 +76,7 @@ class Variable<T = t.Node> {
   }
 }
 
-class StringLiteralVariable extends Variable<t.Node> {
+export class StringLiteralVariable extends Variable<t.Node> {
   constructor(path: t.NodePath<t.Node>, proposedName: string) {
     super(path);
     this.tryToSetNameWith(camelCase(proposedName));
@@ -94,7 +87,7 @@ class StringLiteralVariable extends Variable<t.Node> {
   }
 }
 
-class MemberExpressionVariable extends Variable<
+export class MemberExpressionVariable extends Variable<
   t.MemberExpression | t.OptionalMemberExpression
 > {
   constructor(
@@ -109,7 +102,7 @@ class MemberExpressionVariable extends Variable<
   }
 }
 
-class ShorthandVariable extends Variable<t.ObjectProperty> {
+export class ShorthandVariable extends Variable<t.ObjectProperty> {
   constructor(path: t.NodePath<t.ObjectProperty>) {
     super(path);
     if ("name" in path.node.key) {
diff --git a/src/refactorings/extract/replacement-strategy.ts b/src/refactorings/extract/replacement-strategy.ts
index e5a395af..2aaa8fc3 100644
--- a/src/refactorings/extract/replacement-strategy.ts
+++ b/src/refactorings/extract/replacement-strategy.ts
@@ -1,8 +1,6 @@
 import { Editor } from "../../editor/editor";
 
-export { askReplacementStrategy };
-
-async function askReplacementStrategy(
+export async function askReplacementStrategy(
   otherOccurrences: any[],
   editor: Editor
 ): Promise<ReplacementStrategy> {
diff --git a/src/refactorings/flip-if-else/flip-if-else.ts b/src/refactorings/flip-if-else/flip-if-else.ts
index fba0851e..bd36dd66 100644
--- a/src/refactorings/flip-if-else/flip-if-else.ts
+++ b/src/refactorings/flip-if-else/flip-if-else.ts
@@ -5,9 +5,7 @@ import { last, allButLast } from "../../array";
 
 import { getNegatedBinaryOperator } from "../invert-boolean-logic/invert-boolean-logic";
 
-export { flipIfElse, createVisitor as hasIfElseToFlip };
-
-async function flipIfElse(editor: Editor) {
+export async function flipIfElse(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -44,7 +42,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.IfStatement>) => void
 ): t.Visitor {
diff --git a/src/refactorings/flip-if-else/index.ts b/src/refactorings/flip-if-else/index.ts
index 2ecfdcc9..20873c94 100644
--- a/src/refactorings/flip-if-else/index.ts
+++ b/src/refactorings/flip-if-else/index.ts
@@ -1,4 +1,4 @@
-import { hasIfElseToFlip, flipIfElse } from "./flip-if-else";
+import { createVisitor, flipIfElse } from "./flip-if-else";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Flip if/else",
-    createVisitor: hasIfElseToFlip,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/flip-ternary/flip-ternary.ts b/src/refactorings/flip-ternary/flip-ternary.ts
index f6a108e0..7cd66047 100644
--- a/src/refactorings/flip-ternary/flip-ternary.ts
+++ b/src/refactorings/flip-ternary/flip-ternary.ts
@@ -4,9 +4,7 @@ import * as t from "../../ast";
 
 import { getNegatedBinaryOperator } from "../invert-boolean-logic/invert-boolean-logic";
 
-export { flipTernary, createVisitor as hasTernaryToFlip };
-
-async function flipTernary(editor: Editor) {
+export async function flipTernary(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -32,7 +30,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.ConditionalExpression>) => void
 ): t.Visitor {
diff --git a/src/refactorings/flip-ternary/index.ts b/src/refactorings/flip-ternary/index.ts
index 80665d48..ed2f2ee2 100644
--- a/src/refactorings/flip-ternary/index.ts
+++ b/src/refactorings/flip-ternary/index.ts
@@ -1,4 +1,4 @@
-import { hasTernaryToFlip, flipTernary } from "./flip-ternary";
+import { createVisitor, flipTernary } from "./flip-ternary";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Flip ternary",
-    createVisitor: hasTernaryToFlip,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/inline/find-exported-id-names.ts b/src/refactorings/inline/find-exported-id-names.ts
index e77a0420..a6fbb282 100644
--- a/src/refactorings/inline/find-exported-id-names.ts
+++ b/src/refactorings/inline/find-exported-id-names.ts
@@ -1,8 +1,6 @@
 import * as t from "../../ast";
 
-export { findExportedIdNames };
-
-function findExportedIdNames(scope: t.Node): t.Identifier["name"][] {
+export function findExportedIdNames(scope: t.Node): t.Identifier["name"][] {
   const result: t.Identifier["name"][] = [];
 
   t.traverseNode(scope, {
diff --git a/src/refactorings/inline/inline-function/find-param-matching-id.ts b/src/refactorings/inline/inline-function/find-param-matching-id.ts
index e3c57a99..0d217b8f 100644
--- a/src/refactorings/inline/inline-function/find-param-matching-id.ts
+++ b/src/refactorings/inline/inline-function/find-param-matching-id.ts
@@ -1,7 +1,5 @@
 import * as t from "../../../ast";
 
-export { findParamMatchingId };
-
 /**
  * We use a Composite pattern to find the matching param.
  *
@@ -32,7 +30,7 @@ export { findParamMatchingId };
  *     // Resolving its value with the CallExpression args will return `1`.
  */
 
-function findParamMatchingId(
+export function findParamMatchingId(
   id: t.Identifier,
   params: (t.Node | null)[]
 ): MatchingParam {
diff --git a/src/refactorings/inline/inline-function/inline-function.ts b/src/refactorings/inline/inline-function/inline-function.ts
index 74df0fa6..8a09b364 100644
--- a/src/refactorings/inline/inline-function/inline-function.ts
+++ b/src/refactorings/inline/inline-function/inline-function.ts
@@ -5,9 +5,7 @@ import * as t from "../../../ast";
 import { findParamMatchingId } from "./find-param-matching-id";
 import { findExportedIdNames } from "../find-exported-id-names";
 
-export { inlineFunction };
-
-async function inlineFunction(editor: Editor) {
+export async function inlineFunction(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(code, selection);
 
diff --git a/src/refactorings/inline/inline-variable/find-inlinable-code.ts b/src/refactorings/inline/inline-variable/find-inlinable-code.ts
index 6686eb0a..0b3db948 100644
--- a/src/refactorings/inline/inline-variable/find-inlinable-code.ts
+++ b/src/refactorings/inline/inline-variable/find-inlinable-code.ts
@@ -7,15 +7,7 @@ import { findExportedIdNames } from "../find-exported-id-names";
 import { VariableDeclarator } from "../../../ast";
 import { Position } from "../../../editor/position";
 
-export {
-  findInlinableCode,
-  InlinableTSTypeAlias,
-  InlinableObjectPattern,
-  SingleDeclaration,
-  MultipleDeclarations
-};
-
-function findInlinableCode(
+export function findInlinableCode(
   selection: Selection,
   parent: t.Node,
   declaration: Declaration | VariableDeclarator
@@ -335,7 +327,7 @@ class InlinableJSXElementIdentifier extends InlinableIdentifier {
   }
 }
 
-class InlinableTSTypeAlias implements InlinableCode {
+export class InlinableTSTypeAlias implements InlinableCode {
   shouldExtendSelectionToDeclaration = true;
   codeToRemoveSelection: Selection;
   valueSelection: Selection;
@@ -432,7 +424,7 @@ class CompositeInlinable implements InlinableCode {
     return this.child.updateIdentifiersWith(inlinedCode);
   }
 }
-class SingleDeclaration extends CompositeInlinable {
+export class SingleDeclaration extends CompositeInlinable {
   get codeToRemoveSelection(): Selection {
     const selection = super.codeToRemoveSelection;
 
@@ -444,7 +436,7 @@ class SingleDeclaration extends CompositeInlinable {
   }
 }
 
-class MultipleDeclarations extends CompositeInlinable {
+export class MultipleDeclarations extends CompositeInlinable {
   private previous: t.SelectableNode;
   private next: t.SelectableNode | undefined;
 
@@ -471,7 +463,7 @@ class MultipleDeclarations extends CompositeInlinable {
   }
 }
 
-class InlinableObjectPattern extends CompositeInlinable {
+export class InlinableObjectPattern extends CompositeInlinable {
   private initName: string;
   private property: t.SelectableObjectProperty;
   private previous: t.SelectableObjectProperty | undefined;
diff --git a/src/refactorings/inline/inline-variable/inline-variable.ts b/src/refactorings/inline/inline-variable/inline-variable.ts
index f10f3307..aed6943f 100644
--- a/src/refactorings/inline/inline-variable/inline-variable.ts
+++ b/src/refactorings/inline/inline-variable/inline-variable.ts
@@ -10,9 +10,7 @@ import {
   InlinableTSTypeAlias
 } from "./find-inlinable-code";
 
-export { inlineVariable };
-
-async function inlineVariable(editor: Editor) {
+export async function inlineVariable(editor: Editor) {
   const { code, selection } = editor;
   const inlinableCode = findInlinableCodeInAST(code, selection);
 
diff --git a/src/refactorings/invert-boolean-logic/index.ts b/src/refactorings/invert-boolean-logic/index.ts
index a859dce4..976bcc93 100644
--- a/src/refactorings/invert-boolean-logic/index.ts
+++ b/src/refactorings/invert-boolean-logic/index.ts
@@ -1,5 +1,5 @@
 import {
-  canNegateExpression,
+  createVisitor,
   invertBooleanLogic,
   getNegatedOperator
 } from "./invert-boolean-logic";
@@ -14,7 +14,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Invert boolean logic (De Morgan's law)",
-    createVisitor: canNegateExpression,
+    createVisitor,
     updateMessage(path) {
       const operator = getNegatedOperator(path.node);
       return operator
diff --git a/src/refactorings/invert-boolean-logic/invert-boolean-logic.ts b/src/refactorings/invert-boolean-logic/invert-boolean-logic.ts
index 29c675fd..ac6aadaf 100644
--- a/src/refactorings/invert-boolean-logic/invert-boolean-logic.ts
+++ b/src/refactorings/invert-boolean-logic/invert-boolean-logic.ts
@@ -2,14 +2,7 @@ import { Editor, Code, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  createVisitor as canNegateExpression,
-  invertBooleanLogic,
-  getNegatedOperator
-};
-export { getNegatedBinaryOperator };
-
-async function invertBooleanLogic(editor: Editor) {
+export async function invertBooleanLogic(editor: Editor) {
   const { code, selection } = editor;
   const expression = findNegatableExpression(t.parse(code), selection);
 
@@ -48,7 +41,7 @@ function findNegatableExpression(
   return result;
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.SelectablePath) => void
 ): t.Visitor {
@@ -122,7 +115,7 @@ interface NegatableExpression {
   negatedOperator: NegatedOperator | null;
 }
 
-function getNegatedOperator(node: t.Node): NegatedOperator | null {
+export function getNegatedOperator(node: t.Node): NegatedOperator | null {
   return t.isLogicalExpression(node)
     ? getNegatedLogicalOperator(node.operator)
     : t.isBinaryExpression(node)
@@ -230,7 +223,7 @@ function getNegatedLogicalOperator(
   }
 }
 
-function getNegatedBinaryOperator(
+export function getNegatedBinaryOperator(
   operator: t.BinaryExpression["operator"]
 ): t.BinaryExpression["operator"] {
   switch (operator) {
diff --git a/src/refactorings/lift-up-conditional/index.ts b/src/refactorings/lift-up-conditional/index.ts
index 289b0997..a74b9cf6 100644
--- a/src/refactorings/lift-up-conditional/index.ts
+++ b/src/refactorings/lift-up-conditional/index.ts
@@ -1,4 +1,4 @@
-import { canliftUpConditional, liftUpConditional } from "./lift-up-conditional";
+import { createVisitor, liftUpConditional } from "./lift-up-conditional";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Lift up conditional",
-    createVisitor: canliftUpConditional,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/lift-up-conditional/lift-up-conditional.ts b/src/refactorings/lift-up-conditional/lift-up-conditional.ts
index e03eeda0..31d598b8 100644
--- a/src/refactorings/lift-up-conditional/lift-up-conditional.ts
+++ b/src/refactorings/lift-up-conditional/lift-up-conditional.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { liftUpConditional, createVisitor as canliftUpConditional };
-
-async function liftUpConditional(editor: Editor) {
+export async function liftUpConditional(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -54,7 +52,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.IfStatement>,
diff --git a/src/refactorings/merge-if-statements/index.ts b/src/refactorings/merge-if-statements/index.ts
index 88c89228..4a4a03fb 100644
--- a/src/refactorings/merge-if-statements/index.ts
+++ b/src/refactorings/merge-if-statements/index.ts
@@ -1,4 +1,4 @@
-import { canMergeIfStatements, mergeIfStatements } from "./merge-if-statements";
+import { createVisitor, mergeIfStatements } from "./merge-if-statements";
 
 import { RefactoringWithActionProvider } from "../../types";
 import * as t from "../../ast";
@@ -11,7 +11,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Merge if statements",
-    createVisitor: canMergeIfStatements,
+    createVisitor,
     updateMessage(path: t.NodePath) {
       const { alternate } = path.node as t.IfStatement;
       return alternate ? "Merge else-if" : "Merge if statements";
diff --git a/src/refactorings/merge-if-statements/merge-if-statements.ts b/src/refactorings/merge-if-statements/merge-if-statements.ts
index 54379831..955d21ce 100644
--- a/src/refactorings/merge-if-statements/merge-if-statements.ts
+++ b/src/refactorings/merge-if-statements/merge-if-statements.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { mergeIfStatements, createVisitor as canMergeIfStatements };
-
-async function mergeIfStatements(editor: Editor) {
+export async function mergeIfStatements(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -26,7 +24,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.IfStatement>,
diff --git a/src/refactorings/merge-with-previous-if-statement/index.ts b/src/refactorings/merge-with-previous-if-statement/index.ts
index 22bb1eb3..6490c3c3 100644
--- a/src/refactorings/merge-with-previous-if-statement/index.ts
+++ b/src/refactorings/merge-with-previous-if-statement/index.ts
@@ -1,5 +1,5 @@
 import {
-  canMergeWithPreviousIf,
+  createVisitor,
   mergeWithPreviousIfStatement
 } from "./merge-with-previous-if-statement";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Merge with previous if",
-    createVisitor: canMergeWithPreviousIf,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/merge-with-previous-if-statement/merge-with-previous-if-statement.ts b/src/refactorings/merge-with-previous-if-statement/merge-with-previous-if-statement.ts
index be5d91a4..a8197369 100644
--- a/src/refactorings/merge-with-previous-if-statement/merge-with-previous-if-statement.ts
+++ b/src/refactorings/merge-with-previous-if-statement/merge-with-previous-if-statement.ts
@@ -2,12 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  mergeWithPreviousIfStatement,
-  createVisitor as canMergeWithPreviousIf
-};
-
-async function mergeWithPreviousIfStatement(editor: Editor) {
+export async function mergeWithPreviousIfStatement(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -37,7 +32,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.Statement>) => void
 ): t.Visitor {
diff --git a/src/refactorings/move-statement-down/move-statement-down.ts b/src/refactorings/move-statement-down/move-statement-down.ts
index e2b42190..829987da 100644
--- a/src/refactorings/move-statement-down/move-statement-down.ts
+++ b/src/refactorings/move-statement-down/move-statement-down.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import { Position } from "../../editor/position";
 import * as t from "../../ast";
 
-export { moveStatementDown };
-
-async function moveStatementDown(editor: Editor) {
+export async function moveStatementDown(editor: Editor) {
   const { code, selection } = editor;
   if (selection.isMultiLines) {
     // This should be implemented.
diff --git a/src/refactorings/move-statement-up/move-statement-up.ts b/src/refactorings/move-statement-up/move-statement-up.ts
index ec47f40f..99263dfc 100644
--- a/src/refactorings/move-statement-up/move-statement-up.ts
+++ b/src/refactorings/move-statement-up/move-statement-up.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import { Position } from "../../editor/position";
 import * as t from "../../ast";
 
-export { moveStatementUp };
-
-async function moveStatementUp(editor: Editor) {
+export async function moveStatementUp(editor: Editor) {
   const { code, selection } = editor;
   if (selection.isMultiLines) {
     // This should be implemented.
diff --git a/src/refactorings/move-to-existing-file/move-to-existing-file.ts b/src/refactorings/move-to-existing-file/move-to-existing-file.ts
index 4d531fc8..5b1c2e58 100644
--- a/src/refactorings/move-to-existing-file/move-to-existing-file.ts
+++ b/src/refactorings/move-to-existing-file/move-to-existing-file.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason, RelativePath } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { moveToExistingFile, createVisitor };
-
-async function moveToExistingFile(editor: Editor) {
+export async function moveToExistingFile(editor: Editor) {
   const { code, selection } = editor;
 
   const files = await editor.workspaceFiles();
@@ -114,7 +112,7 @@ function updateOtherFileCode(
   });
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.RootNodePath,
diff --git a/src/refactorings/react/convert-to-pure-component/convert-to-pure-component.ts b/src/refactorings/react/convert-to-pure-component/convert-to-pure-component.ts
index 5136c1b5..b8a2f8b5 100644
--- a/src/refactorings/react/convert-to-pure-component/convert-to-pure-component.ts
+++ b/src/refactorings/react/convert-to-pure-component/convert-to-pure-component.ts
@@ -6,9 +6,7 @@ const pureComponent = reactCodemod.default;
 import { Editor, Code, ErrorReason } from "../../../editor/editor";
 import * as t from "../../../ast";
 
-export { convertToPureComponent };
-
-async function convertToPureComponent(editor: Editor) {
+export async function convertToPureComponent(editor: Editor) {
   const { code } = editor;
   const useArrowsChoice = await editor.askUserChoice([
     { value: true, label: "Use an arrow function" },
diff --git a/src/refactorings/remove-dead-code/index.ts b/src/refactorings/remove-dead-code/index.ts
index 56f42c5c..500eb7ef 100644
--- a/src/refactorings/remove-dead-code/index.ts
+++ b/src/refactorings/remove-dead-code/index.ts
@@ -1,4 +1,4 @@
-import { hasDeadCode, removeDeadCode } from "./remove-dead-code";
+import { createVisitor, removeDeadCode } from "./remove-dead-code";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Remove dead code",
-    createVisitor: hasDeadCode,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/remove-dead-code/remove-dead-code.ts b/src/refactorings/remove-dead-code/remove-dead-code.ts
index d831c874..4b03538f 100644
--- a/src/refactorings/remove-dead-code/remove-dead-code.ts
+++ b/src/refactorings/remove-dead-code/remove-dead-code.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { removeDeadCode, createVisitor as hasDeadCode };
-
-async function removeDeadCode(editor: Editor) {
+export async function removeDeadCode(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -52,7 +50,10 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(selection: Selection, onMatch: OnMatch): t.Visitor {
+export function createVisitor(
+  selection: Selection,
+  onMatch: OnMatch
+): t.Visitor {
   return {
     IfStatement(path) {
       if (!selection.isInsidePath(path)) return;
diff --git a/src/refactorings/remove-redundant-else/index.ts b/src/refactorings/remove-redundant-else/index.ts
index e4906119..b7500398 100644
--- a/src/refactorings/remove-redundant-else/index.ts
+++ b/src/refactorings/remove-redundant-else/index.ts
@@ -1,4 +1,4 @@
-import { hasRedundantElse, removeRedundantElse } from "./remove-redundant-else";
+import { createVisitor, removeRedundantElse } from "./remove-redundant-else";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Remove redundant else",
-    createVisitor: hasRedundantElse,
+    createVisitor,
     isPreferred: true
   }
 };
diff --git a/src/refactorings/remove-redundant-else/remove-redundant-else.ts b/src/refactorings/remove-redundant-else/remove-redundant-else.ts
index f23b3214..098133eb 100644
--- a/src/refactorings/remove-redundant-else/remove-redundant-else.ts
+++ b/src/refactorings/remove-redundant-else/remove-redundant-else.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import { last } from "../../array";
 import * as t from "../../ast";
 
-export { removeRedundantElse, createVisitor as hasRedundantElse };
-
-async function removeRedundantElse(editor: Editor) {
+export async function removeRedundantElse(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -33,7 +31,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.IfStatement>) => void
 ): t.Visitor {
diff --git a/src/refactorings/rename-symbol/rename-symbol.ts b/src/refactorings/rename-symbol/rename-symbol.ts
index 0a391257..98a2aaaa 100644
--- a/src/refactorings/rename-symbol/rename-symbol.ts
+++ b/src/refactorings/rename-symbol/rename-symbol.ts
@@ -1,9 +1,7 @@
 import { Editor, Command, Result, ErrorReason } from "../../editor/editor";
 import * as t from "../../ast";
 
-export { renameSymbol };
-
-async function renameSymbol(editor: Editor) {
+export async function renameSymbol(editor: Editor) {
   // Editor built-in rename works fine => ok to delegate the work for now.
   const result = await editor.delegate(Command.RenameSymbol);
 
diff --git a/src/refactorings/replace-binary-with-assignment/index.ts b/src/refactorings/replace-binary-with-assignment/index.ts
index c1a2b006..3df3115c 100644
--- a/src/refactorings/replace-binary-with-assignment/index.ts
+++ b/src/refactorings/replace-binary-with-assignment/index.ts
@@ -1,6 +1,6 @@
 import {
   replaceBinaryWithAssignment,
-  canReplaceBinaryWithAssignment
+  createVisitor
 } from "./replace-binary-with-assignment";
 
 import * as t from "../../ast";
@@ -15,7 +15,7 @@ const config: RefactoringWithActionProvider = {
   actionProvider: {
     message: "Replace binary with assignment",
     isPreferred: true,
-    createVisitor: canReplaceBinaryWithAssignment,
+    createVisitor,
     updateMessage(path: t.NodePath) {
       const { node } = path as t.NodePath<t.AssignmentExpression>;
 
diff --git a/src/refactorings/replace-binary-with-assignment/replace-binary-with-assignment.ts b/src/refactorings/replace-binary-with-assignment/replace-binary-with-assignment.ts
index 5f9acde5..282b05b6 100644
--- a/src/refactorings/replace-binary-with-assignment/replace-binary-with-assignment.ts
+++ b/src/refactorings/replace-binary-with-assignment/replace-binary-with-assignment.ts
@@ -2,12 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  replaceBinaryWithAssignment,
-  createVisitor as canReplaceBinaryWithAssignment
-};
-
-async function replaceBinaryWithAssignment(editor: Editor) {
+export async function replaceBinaryWithAssignment(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -62,7 +57,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   return result;
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.AssignmentExpression>) => void
 ): t.Visitor {
diff --git a/src/refactorings/simplify-ternary/index.ts b/src/refactorings/simplify-ternary/index.ts
index 4b9b4d8a..0df24968 100644
--- a/src/refactorings/simplify-ternary/index.ts
+++ b/src/refactorings/simplify-ternary/index.ts
@@ -1,4 +1,4 @@
-import { simplifyTernary, canSimplifyTernary } from "./simplify-ternary";
+import { simplifyTernary, createVisitor } from "./simplify-ternary";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Simplify ternary",
-    createVisitor: canSimplifyTernary
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/simplify-ternary/simplify-ternary.ts b/src/refactorings/simplify-ternary/simplify-ternary.ts
index 1ec5a8c2..47e1e5e7 100644
--- a/src/refactorings/simplify-ternary/simplify-ternary.ts
+++ b/src/refactorings/simplify-ternary/simplify-ternary.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { simplifyTernary, createVisitor as canSimplifyTernary };
-
-async function simplifyTernary(editor: Editor) {
+export async function simplifyTernary(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -26,7 +24,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (
     path: t.NodePath<t.ConditionalExpression>,
diff --git a/src/refactorings/split-declaration-and-initialization/index.ts b/src/refactorings/split-declaration-and-initialization/index.ts
index 9c78de66..6cbef5e2 100644
--- a/src/refactorings/split-declaration-and-initialization/index.ts
+++ b/src/refactorings/split-declaration-and-initialization/index.ts
@@ -1,5 +1,5 @@
 import {
-  canSplitDeclarationAndInitialization,
+  createVisitor,
   splitDeclarationAndInitialization
 } from "./split-declaration-and-initialization";
 
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Split declaration and initialization",
-    createVisitor: canSplitDeclarationAndInitialization
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/split-declaration-and-initialization/split-declaration-and-initialization.ts b/src/refactorings/split-declaration-and-initialization/split-declaration-and-initialization.ts
index 434c7723..ba96ab1d 100644
--- a/src/refactorings/split-declaration-and-initialization/split-declaration-and-initialization.ts
+++ b/src/refactorings/split-declaration-and-initialization/split-declaration-and-initialization.ts
@@ -2,12 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  splitDeclarationAndInitialization,
-  createVisitor as canSplitDeclarationAndInitialization
-};
-
-async function splitDeclarationAndInitialization(editor: Editor) {
+export async function splitDeclarationAndInitialization(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -59,7 +54,7 @@ function objectPatternLVals(objectPattern: t.ObjectPattern): t.LVal[] {
     .filter((lval): lval is t.LVal => t.isLVal(lval));
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.VariableDeclaration>) => void
 ): t.Visitor {
diff --git a/src/refactorings/split-if-statement/index.ts b/src/refactorings/split-if-statement/index.ts
index 03556f9e..8cb8b139 100644
--- a/src/refactorings/split-if-statement/index.ts
+++ b/src/refactorings/split-if-statement/index.ts
@@ -1,4 +1,4 @@
-import { canSplitIfStatement, splitIfStatement } from "./split-if-statement";
+import { createVisitor, splitIfStatement } from "./split-if-statement";
 
 import { RefactoringWithActionProvider } from "../../types";
 
@@ -10,7 +10,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Split if statement",
-    createVisitor: canSplitIfStatement
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/split-if-statement/split-if-statement.ts b/src/refactorings/split-if-statement/split-if-statement.ts
index 2dcd83c6..b9286d47 100644
--- a/src/refactorings/split-if-statement/split-if-statement.ts
+++ b/src/refactorings/split-if-statement/split-if-statement.ts
@@ -2,9 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export { splitIfStatement, createVisitor as canSplitIfStatement };
-
-async function splitIfStatement(editor: Editor) {
+export async function splitIfStatement(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -42,7 +40,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath<t.IfStatement>) => void
 ): t.Visitor {
diff --git a/src/refactorings/split-multiple-declarations/index.ts b/src/refactorings/split-multiple-declarations/index.ts
index 03d6bbbf..2ec4c4d8 100644
--- a/src/refactorings/split-multiple-declarations/index.ts
+++ b/src/refactorings/split-multiple-declarations/index.ts
@@ -1,6 +1,6 @@
 import {
   splitMultipleDeclarations,
-  canSplitMultipleDeclarations
+  createVisitor
 } from "./split-multiple-declarations";
 
 import { RefactoringWithActionProvider } from "../../types";
@@ -13,7 +13,7 @@ const config: RefactoringWithActionProvider = {
   },
   actionProvider: {
     message: "Split multiple declarations",
-    createVisitor: canSplitMultipleDeclarations
+    createVisitor
   }
 };
 
diff --git a/src/refactorings/split-multiple-declarations/split-multiple-declarations.ts b/src/refactorings/split-multiple-declarations/split-multiple-declarations.ts
index 9c25902f..2d95f29d 100644
--- a/src/refactorings/split-multiple-declarations/split-multiple-declarations.ts
+++ b/src/refactorings/split-multiple-declarations/split-multiple-declarations.ts
@@ -2,12 +2,7 @@ import { Editor, ErrorReason } from "../../editor/editor";
 import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 
-export {
-  splitMultipleDeclarations,
-  createVisitor as canSplitMultipleDeclarations
-};
-
-async function splitMultipleDeclarations(editor: Editor) {
+export async function splitMultipleDeclarations(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -35,7 +30,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   _selection: Selection,
   onMatch: (path: t.NodePath<t.VariableDeclaration>) => void
 ): t.Visitor {
diff --git a/src/refactorings/toggle-braces/toggle-braces.ts b/src/refactorings/toggle-braces/toggle-braces.ts
index 35bdcb2b..ae1dce55 100644
--- a/src/refactorings/toggle-braces/toggle-braces.ts
+++ b/src/refactorings/toggle-braces/toggle-braces.ts
@@ -3,9 +3,7 @@ import { Selection } from "../../editor/selection";
 import * as t from "../../ast";
 import { Position } from "../../editor/position";
 
-export { toggleBraces, createVisitor };
-
-async function toggleBraces(editor: Editor) {
+export async function toggleBraces(editor: Editor) {
   const { code, selection } = editor;
   const updatedCode = updateCode(t.parse(code), selection);
 
@@ -27,7 +25,7 @@ function updateCode(ast: t.AST, selection: Selection): t.Transformed {
   );
 }
 
-function createVisitor(
+export function createVisitor(
   selection: Selection,
   onMatch: (path: t.NodePath, toggleBraces: ToggleBraces) => void
 ): t.Visitor {
diff --git a/src/vscode-configuration.ts b/src/vscode-configuration.ts
index 5023d2d3..f6237283 100644
--- a/src/vscode-configuration.ts
+++ b/src/vscode-configuration.ts
@@ -1,8 +1,6 @@
 import * as vscode from "vscode";
 
-export { getIgnoredFolders, getIgnoredPatterns, shouldShowInQuickFix };
-
-function getIgnoredFolders(): string[] {
+export function getIgnoredFolders(): string[] {
   const result = vscode.workspace
     .getConfiguration("abracadabra")
     .get("ignoredFolders");
@@ -17,7 +15,7 @@ function getIgnoredFolders(): string[] {
   return result;
 }
 
-function getIgnoredPatterns(): string[] {
+export function getIgnoredPatterns(): string[] {
   const result = vscode.workspace
     .getConfiguration("abracadabra")
     .get("ignoredPatterns");
@@ -32,7 +30,7 @@ function getIgnoredPatterns(): string[] {
   return result;
 }
 
-function shouldShowInQuickFix(refactoringKey: string): boolean {
+export function shouldShowInQuickFix(refactoringKey: string): boolean {
   const result = vscode.workspace
     .getConfiguration("abracadabra")
     .get(`${refactoringKey}.showInQuickFix`);
diff --git a/yarn.lock b/yarn.lock
index 432b18f6..869dc73c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4307,9 +4307,9 @@ flow-parser@0.*:
   integrity sha512-t5y9QoOegJuY+LCIjh0p6SGF7ItsxG5ycQApTSqWloutUZQ2gC0f6wMu91dab0/SSj2vH41bu5pDTLuvtP49ng==
 
 follow-redirects@^1.13.2:
-  version "1.14.8"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
-  integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
+  version "1.14.7"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
+  integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
 
 for-in@^1.0.2:
   version "1.0.2"
@@ -7625,9 +7625,9 @@ simple-concat@^1.0.0:
   integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
 
 simple-get@^3.0.3:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
-  integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
+  integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
   dependencies:
     decompress-response "^4.2.0"
     once "^1.3.1"


Test patch:
diff --git a/src/editor/editor-contract-test.ts b/src/editor/editor-contract-test.ts
index 9df55d78..b4fd865e 100644
--- a/src/editor/editor-contract-test.ts
+++ b/src/editor/editor-contract-test.ts
@@ -6,8 +6,6 @@ import { Editor, Code, RelativePath } from "./editor";
 import { Position } from "./position";
 import { Selection } from "./selection";
 
-export { createEditorContractTests };
-
 /**
  * This is a contract tests factory.
  *
@@ -22,7 +20,7 @@ export { createEditorContractTests };
  * - It tells you if everything still work when you upgrade the adapter
  */
 
-function createEditorContractTests(
+export function createEditorContractTests(
   createEditorOn: (code: Code, position?: Position) => Promise<Editor>,
   cleanUp: () => Promise<void> = async () => {}
 ) {
diff --git a/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.test.ts b/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.test.ts
index 9d6363b3..0379b08d 100644
--- a/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.test.ts
+++ b/src/refactorings/convert-for-to-for-each/convert-for-to-for-each.test.ts
@@ -3,10 +3,7 @@ import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
 import { testEach } from "../../tests-helpers";
 import * as t from "../../ast";
 
-import {
-  canConvertForLoop,
-  convertForToForEach
-} from "./convert-for-to-for-each";
+import { createVisitor, convertForToForEach } from "./convert-for-to-for-each";
 import { Selection } from "../../editor/selection";
 
 describe("Convert For To Foreach", () => {
@@ -357,7 +354,7 @@ for (let entry of typedArray) {
 
     t.traverseAST(t.parse(code), {
       enter(path) {
-        const visitor = canConvertForLoop(Selection.cursorAt(0, 0), onMatch);
+        const visitor = createVisitor(Selection.cursorAt(0, 0), onMatch);
         const visitorNode = visitor[path.node.type];
         if (typeof visitorNode === "function") {
           // @ts-expect-error visitor can expect `NodePath<File>` but `path` is typed as `NodePath<Node>`. It should be OK at runtime.
diff --git a/src/refactorings/convert-to-template-literal/convert-to-template-literal.test.ts b/src/refactorings/convert-to-template-literal/convert-to-template-literal.test.ts
index 11c944ee..2360b62e 100644
--- a/src/refactorings/convert-to-template-literal/convert-to-template-literal.test.ts
+++ b/src/refactorings/convert-to-template-literal/convert-to-template-literal.test.ts
@@ -5,7 +5,7 @@ import { testEach } from "../../tests-helpers";
 import * as t from "../../ast";
 import {
   convertToTemplateLiteral,
-  canConvertToTemplateLiteral
+  createVisitor
 } from "./convert-to-template-literal";
 
 describe("Convert To Template Literal", () => {
@@ -33,7 +33,7 @@ describe("Convert To Template Literal", () => {
       let canConvert = false;
       t.traverseAST(
         ast,
-        canConvertToTemplateLiteral(selection, () => (canConvert = true))
+        createVisitor(selection, () => (canConvert = true))
       );
 
       expect(canConvert).toBeFalsy();
diff --git a/src/refactorings/invert-boolean-logic/invert-boolean-logic.test.ts b/src/refactorings/invert-boolean-logic/invert-boolean-logic.test.ts
index d586f358..f5955d30 100644
--- a/src/refactorings/invert-boolean-logic/invert-boolean-logic.test.ts
+++ b/src/refactorings/invert-boolean-logic/invert-boolean-logic.test.ts
@@ -4,10 +4,7 @@ import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
 import * as t from "../../ast";
 import { testEach } from "../../tests-helpers";
 
-import {
-  invertBooleanLogic,
-  canNegateExpression
-} from "./invert-boolean-logic";
+import { invertBooleanLogic, createVisitor } from "./invert-boolean-logic";
 
 describe("Invert Boolean Logic", () => {
   testEach<{ code: Code }>(
@@ -216,7 +213,7 @@ describe("Finding invertable expression (quick fix)", () => {
       let canNegate = false;
       t.traverseAST(
         t.parse(code),
-        canNegateExpression(selection, () => (canNegate = true))
+        createVisitor(selection, () => (canNegate = true))
       );
 
       expect(canNegate).toBe(true);
@@ -248,7 +245,7 @@ describe("Finding invertable expression (quick fix)", () => {
       let canNegate = false;
       t.traverseAST(
         t.parse(code),
-        canNegateExpression(selection, () => (canNegate = true))
+        createVisitor(selection, () => (canNegate = true))
       );
 
       expect(canNegate).toBe(false);
diff --git a/src/refactorings/merge-if-statements/merge-if-statements.test.ts b/src/refactorings/merge-if-statements/merge-if-statements.test.ts
index 19a4e57c..710c06bf 100644
--- a/src/refactorings/merge-if-statements/merge-if-statements.test.ts
+++ b/src/refactorings/merge-if-statements/merge-if-statements.test.ts
@@ -2,7 +2,7 @@ import { Code, ErrorReason } from "../../editor/editor";
 import { InMemoryEditor } from "../../editor/adapters/in-memory-editor";
 import { testEach } from "../../tests-helpers";
 
-import { canMergeIfStatements, mergeIfStatements } from "./merge-if-statements";
+import { createVisitor, mergeIfStatements } from "./merge-if-statements";
 
 describe("Merge If Statements", () => {
   testEach<{ code: Code; expected: Code }>(
@@ -498,7 +498,7 @@ describe("Merge If Statements", () => {
 }`;
     const editor = new InMemoryEditor(code);
 
-    await expect(canMergeIfStatements).not.toMatchEditor(editor);
+    await expect(createVisitor).not.toMatchEditor(editor);
   });
 
   it("should not match else-if with different returned values", async () => {
@@ -510,6 +510,6 @@ describe("Merge If Statements", () => {
 }`;
     const editor = new InMemoryEditor(code);
 
-    await expect(canMergeIfStatements).not.toMatchEditor(editor);
+    await expect(createVisitor).not.toMatchEditor(editor);
   });
 });
diff --git a/src/tests-helpers.ts b/src/tests-helpers.ts
index 5a190337..dd49c242 100644
--- a/src/tests-helpers.ts
+++ b/src/tests-helpers.ts
@@ -1,6 +1,4 @@
-export { testEach };
-
-function testEach<T>(
+export function testEach<T>(
   label: string,
   assertions: (Assertion & T)[],
   fn: (...args: (Assertion & T)[]) => any