Repository: elm-tooling/elm-language-server
Pull request #541 (2021-03-03 18:59:06)

Add remove unused fix all code action
Hooks up ElmLs code actions to the `CodeActionProvider` which make "Fix all" actions work.


Patch:
diff --git a/src/compiler/diagnostics.ts b/src/compiler/diagnostics.ts
index b4ed1e1..81d9d36 100644
--- a/src/compiler/diagnostics.ts
+++ b/src/compiler/diagnostics.ts
@@ -1,5 +1,9 @@
 /* eslint-disable @typescript-eslint/naming-convention */
-import { DiagnosticSeverity, Range } from "vscode-languageserver";
+import {
+  DiagnosticSeverity,
+  DiagnosticTag,
+  Range,
+} from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
 import { PositionUtil } from "../positionUtil";
 import { DiagnosticSource } from "../providers/diagnostics/diagnosticSource";
@@ -12,6 +16,7 @@ export interface Diagnostic {
   severity: DiagnosticSeverity;
   range: Range;
   uri: string;
+  tags?: DiagnosticTag[];
 }
 
 function format(text: string, ...args: (string | number)[]): string {
diff --git a/src/providers/codeActionLs/index.ts b/src/providers/codeActionLs/index.ts
new file mode 100644
index 0000000..de79f41
--- /dev/null
+++ b/src/providers/codeActionLs/index.ts
@@ -0,0 +1,1 @@
+import "./removeUnusedCodeAction";
diff --git a/src/providers/codeActionLs/removeUnusedCodeAction.ts b/src/providers/codeActionLs/removeUnusedCodeAction.ts
new file mode 100644
index 0000000..1556417
--- /dev/null
+++ b/src/providers/codeActionLs/removeUnusedCodeAction.ts
@@ -0,0 +1,153 @@
+import { TextEdit, Range, Position } from "vscode-languageserver";
+import { ISourceFile } from "../../compiler/forest";
+import { RefactorEditUtils } from "../../util/refactorEditUtils";
+import { TreeUtils } from "../../util/treeUtils";
+import { Utils } from "../../util/utils";
+import { CodeActionProvider } from "../codeActionProvider";
+import {
+  convertFromCompilerDiagnostic,
+  IDiagnostic,
+} from "../diagnostics/diagnosticsProvider";
+
+const errorCodes = [
+  "unused_import",
+  "unused_imported_value",
+  "unused_alias",
+  "unused_top_level",
+  "unused_pattern",
+];
+const fixId = "remove_unused";
+CodeActionProvider.registerCodeAction({
+  errorCodes,
+  fixId,
+  getCodeActions: (params) => {
+    return (<IDiagnostic[]>params.context.diagnostics)
+      .map((diagnostic) => {
+        const { title, edits } = getEditsForDiagnostic(
+          diagnostic,
+          params.sourceFile,
+        );
+
+        if (title) {
+          return CodeActionProvider.getCodeAction(params, title, edits);
+        }
+      })
+      .filter(Utils.notUndefined.bind(this));
+  },
+  getFixAllCodeAction: (params) => {
+    return CodeActionProvider.getFixAllCodeAction(
+      "Remove all unused code",
+      params,
+      errorCodes,
+      fixId,
+      (edits, diagnostic) => {
+        edits.push(
+          ...getEditsForDiagnostic(
+            convertFromCompilerDiagnostic(diagnostic),
+            params.sourceFile,
+          ).edits,
+        );
+      },
+    );
+  },
+});
+
+function getEditsForDiagnostic(
+  diagnostic: IDiagnostic,
+  sourceFile: ISourceFile,
+): { title?: string; edits: TextEdit[] } {
+  switch (diagnostic.data.code) {
+    case "unused_import": {
+      const node = TreeUtils.getNamedDescendantForPosition(
+        sourceFile.tree.rootNode,
+        diagnostic.range.end,
+      );
+
+      const moduleName = node.childForFieldName("moduleName");
+      return {
+        title: `Remove unused import \`${moduleName?.text ?? node.text}\``,
+        edits: [
+          TextEdit.del(
+            Range.create(
+              diagnostic.range.start,
+              Position.create(diagnostic.range.end.line + 1, 0),
+            ),
+          ),
+        ],
+      };
+    }
+
+    case "unused_alias": {
+      const node = TreeUtils.getNamedDescendantForPosition(
+        sourceFile.tree.rootNode,
+        diagnostic.range.end,
+      );
+
+      return {
+        title: `Remove unused alias \`${node.text}\``,
+        edits: [
+          TextEdit.del({
+            start: {
+              line: diagnostic.range.start.line,
+              character: diagnostic.range.start.character - 1,
+            },
+            end: diagnostic.range.end,
+          }),
+        ],
+      };
+    }
+
+    case "unused_pattern": {
+      const node = TreeUtils.getNamedDescendantForPosition(
+        sourceFile.tree.rootNode,
+        diagnostic.range.start,
+      );
+
+      const edit =
+        node.parent?.parent?.type === "record_pattern"
+          ? RefactorEditUtils.removeRecordPatternValue(node.parent)
+          : TextEdit.replace(diagnostic.range, "_");
+
+      return { title: `Fix unused pattern \`${node.text}\``, edits: [edit] };
+    }
+
+    case "unused_imported_value": {
+      const node = TreeUtils.getNamedDescendantForPosition(
+        sourceFile.tree.rootNode,
+        diagnostic.range.start,
+      );
+
+      const importClause = TreeUtils.findParentOfType("import_clause", node);
+
+      if (!importClause) {
+        break;
+      }
+
+      const moduleName = TreeUtils.findFirstNamedChildOfType(
+        "upper_case_qid",
+        importClause,
+      );
+
+      if (!moduleName) {
+        break;
+      }
+
+      const removeValueEdit = RefactorEditUtils.removeValueFromImport(
+        sourceFile.tree,
+        moduleName.text,
+        node.text,
+      );
+
+      if (removeValueEdit) {
+        return {
+          title: `Remove unused ${
+            node.type === "exposed_type" ? "type" : "value"
+          } \`${node.text}\``,
+          edits: [removeValueEdit],
+        };
+      }
+    }
+  }
+
+  return { edits: [] };
+}
diff --git a/src/providers/codeActionProvider.ts b/src/providers/codeActionProvider.ts
index d298c96..d4a55b0 100644
--- a/src/providers/codeActionProvider.ts
+++ b/src/providers/codeActionProvider.ts
@@ -9,6 +9,7 @@ import {
   Command,
   Connection,
   Diagnostic as LspDiagnostic,
+  DiagnosticSeverity,
   Position,
   Range,
   TextEdit,
@@ -21,13 +22,15 @@ import { MultiMap } from "../util/multiMap";
 import { Settings } from "../util/settings";
 import { Diagnostic } from "../compiler/diagnostics";
 import {
-  convertFromAnalyzerDiagnostic,
+  convertFromCompilerDiagnostic,
   DiagnosticsProvider,
   IDiagnostic,
 } from "./diagnostics/diagnosticsProvider";
-import { ElmLsDiagnostics } from "./diagnostics/elmLsDiagnostics";
 import { ElmMakeDiagnostics } from "./diagnostics/elmMakeDiagnostics";
-import { diagnosticsEquals } from "./diagnostics/fileDiagnostics";
+import {
+  DiagnosticKind,
+  diagnosticsEquals,
+} from "./diagnostics/fileDiagnostics";
 import { ExposeUnexposeHandler } from "./handlers/exposeUnexposeHandler";
 import { MoveRefactoringHandler } from "./handlers/moveRefactoringHandler";
 import { ICodeActionParams } from "./paramsExtensions";
@@ -66,7 +69,6 @@ export class CodeActionProvider {
   private connection: Connection;
   private settings: Settings;
   private elmMake: ElmMakeDiagnostics;
-  private elmDiagnostics: ElmLsDiagnostics;
   private diagnosticsProvider: DiagnosticsProvider;
 
   private static errorCodeToRegistrationMap = new MultiMap<
@@ -81,7 +83,6 @@ export class CodeActionProvider {
   constructor() {
     this.settings = container.resolve("Settings");
     this.elmMake = container.resolve(ElmMakeDiagnostics);
-    this.elmDiagnostics = container.resolve(ElmLsDiagnostics);
     this.connection = container.resolve<Connection>("Connection");
     this.diagnosticsProvider = container.resolve(DiagnosticsProvider);
 
@@ -134,11 +135,25 @@ export class CodeActionProvider {
     this.refactorRegistrations.set(name, registration);
   }
 
-  private static getDiagnostics(params: ICodeActionParams): Diagnostic[] {
+  private static getDiagnostics(
+    params: ICodeActionParams,
+    diagnosticProvider: DiagnosticsProvider,
+  ): Diagnostic[] {
     return [
       ...params.program.getSyntacticDiagnostics(params.sourceFile),
       ...params.program.getSemanticDiagnostics(params.sourceFile),
       ...params.program.getSuggestionDiagnostics(params.sourceFile),
+      ...diagnosticProvider
+        .getCurrentDiagnostics(params.sourceFile.uri, DiagnosticKind.ElmLS)
+        .map((diag) => ({
+          message: diag.message,
+          source: diag.source,
+          severity: diag.severity ?? DiagnosticSeverity.Warning,
+          range: diag.range,
+          code: diag.data.code,
+          uri: diag.data.uri,
+          tags: diag.tags,
+        })),
     ];
   }
 
@@ -147,14 +162,17 @@ export class CodeActionProvider {
     errorCodes: string[],
     callback: (diagnostic: Diagnostic) => void,
   ): void {
-    CodeActionProvider.getDiagnostics(params).forEach((diagnostic) => {
-      if (
-        typeof diagnostic.code === "string" &&
-        errorCodes.includes(diagnostic.code)
-      ) {
-        callback(diagnostic);
-      }
-    });
+    const diagnosticProvider = container.resolve(DiagnosticsProvider);
+    CodeActionProvider.getDiagnostics(params, diagnosticProvider).forEach(
+      (diagnostic) => {
+        if (
+          typeof diagnostic.code === "string" &&
+          errorCodes.includes(diagnostic.code)
+        ) {
+          callback(diagnostic);
+        }
+      },
+    );
   }
 
   public static getCodeAction(
@@ -180,10 +198,10 @@ export class CodeActionProvider {
     params: ICodeActionParams,
     errorCodes: string[],
     fixId: string,
-    callback: (edits: TextEdit[], diagnostic: LspDiagnostic) => void,
+    callback: (edits: TextEdit[], diagnostic: Diagnostic) => void,
     callbackChanges?: (
       edits: { [uri: string]: TextEdit[] },
-      diagnostic: LspDiagnostic,
+      diagnostic: Diagnostic,
     ) => void,
   ): CodeAction {
     const edits: TextEdit[] = [];
@@ -193,7 +211,7 @@ export class CodeActionProvider {
           [params.sourceFile.uri]: edits,
         };
 
-    const diagnostics: LspDiagnostic[] = [];
+    const diagnostics: Diagnostic[] = [];
     CodeActionProvider.forEachDiagnostic(params, errorCodes, (diagnostic) => {
       diagnostics.push(diagnostic);
       if (callbackChanges) {
@@ -215,7 +233,6 @@ export class CodeActionProvider {
   protected onCodeAction(params: ICodeActionParams): CodeAction[] | undefined {
     this.connection.console.info("A code action was requested");
     const make = this.elmMake.onCodeAction(params);
-    const elmDiagnostics = this.elmDiagnostics.onCodeAction(params);
 
     const results: CodeAction[] = [];
 
@@ -247,12 +264,15 @@ export class CodeActionProvider {
                 // Check if there is already a "fix all" code action for this fix
                 (codeAction) => /* fixId */ codeAction.data === reg.fixId,
               ) &&
-              CodeActionProvider.getDiagnostics(params).some(
+              CodeActionProvider.getDiagnostics(
+                params,
+                this.diagnosticsProvider,
+              ).some(
                 (diag) =>
                   !diagnosticsEquals(
-                    convertFromAnalyzerDiagnostic(diag),
+                    convertFromCompilerDiagnostic(diag),
                     diagnostic,
-                  ) && diag.code === diagnostic.data.code,
+                  ) && reg.errorCodes.includes(diag.code),
               )
             ) {
               const fixAllCodeAction = reg.getFixAllCodeAction(params);
@@ -274,7 +294,7 @@ export class CodeActionProvider {
       ).flatMap((registration) => registration.getAvailableActions(params)),
     );
 
-    return [...results, ...make, ...elmDiagnostics];
+    return [...results, ...make];
   }
 
   private onCodeActionResolve(
diff --git a/src/providers/diagnostics/diagnosticsProvider.ts b/src/providers/diagnostics/diagnosticsProvider.ts
index 92afd54..bb466dc 100644
--- a/src/providers/diagnostics/diagnosticsProvider.ts
+++ b/src/providers/diagnostics/diagnosticsProvider.ts
@@ -47,7 +47,7 @@ export interface IDiagnostic extends Omit<LspDiagnostic, "code"> {
   };
 }
 
-export function convertFromAnalyzerDiagnostic(diag: Diagnostic): IDiagnostic {
+export function convertFromCompilerDiagnostic(diag: Diagnostic): IDiagnostic {
   return {
     message: diag.message,
     source: diag.source,
@@ -57,6 +57,7 @@ export function convertFromAnalyzerDiagnostic(diag: Diagnostic): IDiagnostic {
       uri: diag.uri,
       code: diag.code,
     },
+    tags: diag.tags,
   };
 }
 
@@ -218,7 +219,14 @@ export class DiagnosticsProvider {
     return result;
   }
 
-  public getCurrentDiagnostics(uri: string): IDiagnostic[] {
+  public getCurrentDiagnostics(
+    uri: string,
+    kind?: DiagnosticKind,
+  ): IDiagnostic[] {
+    if (kind) {
+      return this.currentDiagnostics.get(uri)?.getForKind(kind) ?? [];
+    }
+
     return this.currentDiagnostics.get(uri)?.get() ?? [];
   }
 
@@ -367,7 +375,7 @@ export class DiagnosticsProvider {
                 DiagnosticKind.Syntactic,
                 program
                   .getSyntacticDiagnostics(sourceFile)
-                  .map(convertFromAnalyzerDiagnostic),
+                  .map(convertFromCompilerDiagnostic),
               );
 
               if (this.changeSeq !== seq) {
@@ -387,7 +395,7 @@ export class DiagnosticsProvider {
                 this.updateDiagnostics(
                   uri,
                   DiagnosticKind.Semantic,
-                  diagnostics.map(convertFromAnalyzerDiagnostic),
+                  diagnostics.map(convertFromCompilerDiagnostic),
                 );
 
                 next.immediate(() => {
@@ -399,7 +407,7 @@ export class DiagnosticsProvider {
                         sourceFile,
                         serverCancellationToken,
                       )
-                      .map(convertFromAnalyzerDiagnostic),
+                      .map(convertFromCompilerDiagnostic),
                   );
 
                   if (this.changeSeq !== seq) {
diff --git a/src/providers/diagnostics/elmLsDiagnostics.ts b/src/providers/diagnostics/elmLsDiagnostics.ts
index a498ac0..715933a 100644
--- a/src/providers/diagnostics/elmLsDiagnostics.ts
+++ b/src/providers/diagnostics/elmLsDiagnostics.ts
@@ -2,15 +2,10 @@
 import { ISourceFile } from "../../compiler/forest";
 import { container } from "tsyringe";
 import {
-  CodeAction,
-  CodeActionKind,
-  CodeActionParams,
   Connection,
   DiagnosticSeverity,
   DiagnosticTag,
-  Position,
   Range,
-  TextEdit,
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import {
@@ -24,7 +19,6 @@ import {
 import { IProgram } from "../../compiler/program";
 import { PositionUtil } from "../../positionUtil";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
-import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
 import { Utils } from "../../util/utils";
 import { IDiagnostic } from "./diagnosticsProvider";
@@ -517,153 +511,6 @@ export class ElmLsDiagnostics {
     return elmAnalyseJson;
   }
 
-  public onCodeAction(params: CodeActionParams): CodeAction[] {
-    const { uri } = params.textDocument;
-    const elmDiagnostics: IDiagnostic[] = params.context.diagnostics.filter(
-      (diagnostic) => diagnostic.source === "ElmLS",
-    ) as IDiagnostic[];
-
-    return this.convertDiagnosticsToCodeActions(elmDiagnostics, uri);
-  }
-
-  private convertDiagnosticsToCodeActions(
-    diagnostics: IDiagnostic[],
-    uri: string,
-  ): CodeAction[] {
-    const result: CodeAction[] = [];
-
-    const program = this.elmWorkspaceMatcher.getProgramFor(URI.parse(uri));
-
-    const forest = program.getForest();
-
-    const sourceFile = forest.getByUri(uri);
-
-    if (sourceFile) {
-      diagnostics.forEach((diagnostic) => {
-        if (diagnostic.data.code === "unused_imported_value") {
-          const node = TreeUtils.getNamedDescendantForPosition(
-            sourceFile.tree.rootNode,
-            diagnostic.range.start,
-          );
-
-          const importClause = TreeUtils.findParentOfType(
-            "import_clause",
-            node,
-          );
-
-          if (!importClause) {
-            return;
-          }
-
-          const moduleName = TreeUtils.findFirstNamedChildOfType(
-            "upper_case_qid",
-            importClause,
-          );
-
-          if (!moduleName) {
-            return;
-          }
-
-          const removeValueEdit = RefactorEditUtils.removeValueFromImport(
-            sourceFile.tree,
-            moduleName.text,
-            node.text,
-          );
-
-          if (removeValueEdit) {
-            result.push({
-              diagnostics: [diagnostic],
-              edit: {
-                changes: {
-                  [uri]: [removeValueEdit],
-                },
-              },
-              kind: CodeActionKind.QuickFix,
-              title: `Remove unused ${
-                node.type === "exposed_type" ? "type" : "value"
-              } \`${node.text}\``,
-            });
-          }
-        }
-
-        if (diagnostic.data.code === "unused_import") {
-          const node = TreeUtils.getNamedDescendantForPosition(
-            sourceFile.tree.rootNode,
-            diagnostic.range.end,
-          );
-
-          const moduleName = node.childForFieldName("moduleName");
-
-          result.push({
-            diagnostics: [diagnostic],
-            edit: {
-              changes: {
-                [uri]: [
-                  TextEdit.del(
-                    Range.create(
-                      diagnostic.range.start,
-                      Position.create(diagnostic.range.end.line + 1, 0),
-                    ),
-                  ),
-                ],
-              },
-            },
-            kind: CodeActionKind.QuickFix,
-            title: `Remove unused import \`${moduleName?.text ?? node.text}\``,
-          });
-        }
-
-        if (diagnostic.data.code === "unused_alias") {
-          const node = TreeUtils.getNamedDescendantForPosition(
-            sourceFile.tree.rootNode,
-            diagnostic.range.end,
-          );
-
-          result.push({
-            diagnostics: [diagnostic],
-            edit: {
-              changes: {
-                [uri]: [
-                  TextEdit.del({
-                    start: {
-                      line: diagnostic.range.start.line,
-                      character: diagnostic.range.start.character - 1,
-                    },
-                    end: diagnostic.range.end,
-                  }),
-                ],
-              },
-            },
-            kind: CodeActionKind.QuickFix,
-            title: `Remove unused alias \`${node.text}\``,
-          });
-        }
-
-        if (diagnostic.data.code === "unused_pattern") {
-          const node = TreeUtils.getNamedDescendantForPosition(
-            sourceFile.tree.rootNode,
-            diagnostic.range.start,
-          );
-
-          const edit =
-            node.parent?.parent?.type === "record_pattern"
-              ? RefactorEditUtils.removeRecordPatternValue(node.parent)
-              : TextEdit.replace(diagnostic.range, "_");
-
-          result.push({
-            diagnostics: [diagnostic],
-            edit: {
-              changes: { [uri]: [edit] },
-            },
-            kind: CodeActionKind.QuickFix,
-            title: `Fix unused pattern \`${node.text}\``,
-          });
-        }
-      });
-    }
-    return result;
-  }
-
   private getUnusedImportDiagnostics(tree: Tree): IDiagnostic[] {
     const diagnostics: IDiagnostic[] = [];
 
diff --git a/src/providers/index.ts b/src/providers/index.ts
index 0d09846..fe56397 100644
--- a/src/providers/index.ts
+++ b/src/providers/index.ts
@@ -15,3 +15,4 @@ export * from "./renameProvider";
 export * from "./workspaceSymbolProvider";
 export * from "./linkedEditingRangesProvider";
 import "./codeAction";
+import "./codeActionLs";


Test patch:
diff --git a/test/codeActionTests/codeActionTestBase.ts b/test/codeActionTests/codeActionTestBase.ts
index 2be2e88..f98e2b2 100644
--- a/test/codeActionTests/codeActionTestBase.ts
+++ b/test/codeActionTests/codeActionTestBase.ts
@@ -4,7 +4,7 @@ import { URI } from "vscode-uri";
 import { IProgram } from "../../src/compiler/program";
 import {
   CodeActionProvider,
-  convertFromAnalyzerDiagnostic,
+  convertFromCompilerDiagnostic,
 } from "../../src/providers";
 import { ICodeActionParams } from "../../src/providers/paramsExtensions";
 import { Utils } from "../../src/util/utils";
@@ -105,7 +105,7 @@ export async function testCodeAction(
           ...program.getSuggestionDiagnostics(sourceFile),
         ]
           .filter((diag) => Utils.rangeOverlaps(diag.range, range))
-          .map(convertFromAnalyzerDiagnostic),
+          .map(convertFromCompilerDiagnostic),
       },
     }) ?? [];
 
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index e6ff371..646b0cd 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -1,6 +1,6 @@
 import { URI } from "vscode-uri";
 import { SyntaxNode } from "web-tree-sitter";
-import { convertFromAnalyzerDiagnostic } from "../../src/providers";
+import { convertFromCompilerDiagnostic } from "../../src/providers";
 import { diagnosticsEquals } from "../../src/providers/diagnostics/fileDiagnostics";
 import { TreeUtils } from "../../src/util/treeUtils";
 import {
@@ -51,7 +51,7 @@ composeR f g x =
 gt : comparable -> comparable -> Bool
 gt =
   Elm.Kernel.Utils.gt
-  
+
 type Order = LT | EQ | GT
 
 negate : number -> number
@@ -128,13 +128,13 @@ describe("test elm diagnostics", () => {
     }
 
     const expected = expectedDiagnostics.map((exp) =>
-      convertFromAnalyzerDiagnostic(
+      convertFromCompilerDiagnostic(
         error(nodeAtPosition, exp.message, ...exp.args),
       ),
     );
 
     const diagnosticsEqual = Utils.arrayEquals(
-      diagnostics.map(convertFromAnalyzerDiagnostic),
+      diagnostics.map(convertFromCompilerDiagnostic),
       expected,
       diagnosticsEquals,
     );
@@ -166,7 +166,7 @@ concat comparators a b =
 
         comparator :: rest ->
             case comparator a b of
-                EQ -> 
+                EQ ->
                     concat rest a b
 
                 order ->
@@ -334,7 +334,7 @@ func2 =
                 "text"
 
             else
-                func2 n 
+                func2 n
     in
     go
   `;
@@ -422,7 +422,7 @@ func =
 
             else
                 to n
- 
+
         to =
             another
 
@@ -618,7 +618,7 @@ func =
     const source3 = `
     --@ Test.elm
 func =
-    func2 
+    func2
 
 
 func2 =
@@ -754,7 +754,7 @@ type alias Test =
 
 type alias Test2 =
           --^
-    Test3 
+    Test3
 
 type alias Test3 =
     Test2
@@ -775,7 +775,7 @@ type alias Test =
     }
 
 type alias Test2 =
-    Test3 
+    Test3
 
 type alias Test3 =
     { field : Test
@@ -850,7 +850,7 @@ import App exposing (Program)
 import Platform exposing (..)
 
 foo : Program
-      --^ 
+      --^
 foo =
     ""
 
@@ -1139,7 +1139,7 @@ foo =
 --@ App.elm
 module App exposing (..)
 
-foo = 
+foo =
     ""
 `;
     await testTypeInference(
@@ -1166,7 +1166,7 @@ foo =
 --@ App.elm
 module App exposing (..)
 
-foo = 
+foo =
     ""
 `;
     await testTypeInference(
@@ -1193,7 +1193,7 @@ foo =
 --@ App.elm
 module App exposing (..)
 
-foo = 
+foo =
     ""
 `;
     await testTypeInference(
@@ -1373,7 +1373,7 @@ func result =
     --^
         Nothing ->
             ""
-        
+
         Just (Just a) ->
             a
   `;
@@ -1417,7 +1417,7 @@ func result =
     --^
         a :: b :: _ ->
             ""
-          
+
         a :: [] ->
             ""