Repository: elm-tooling/elm-language-server
Pull request #382 (2020-12-01 04:19:13)

Remove use of Diagnostic code property
Adds a new interface `IDiagnostic` for the server that extends `Diagnostic` and ensures the `data` property has a `uri` and `code` and that we always omit the top level `code`. Also defined a new `Diagnostic` interface for the analyzer API so it doesn't rely on LSP. 


Patch:
diff --git a/src/elmWorkspace.ts b/src/elmWorkspace.ts
index 88c2623..a757959 100644
--- a/src/elmWorkspace.ts
+++ b/src/elmWorkspace.ts
@@ -4,7 +4,7 @@ import os from "os";
 import path from "path";
 import { container } from "tsyringe";
 import util from "util";
-import { Connection, Diagnostic } from "vscode-languageserver";
+import { Connection } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import Parser, { Tree } from "web-tree-sitter";
 import { ICancellationToken } from "./cancellation";
@@ -15,6 +15,7 @@ import {
   PossibleImportsCache,
 } from "./util/possibleImportsCache";
 import { Settings } from "./util/settings";
+import { Diagnostic } from "./util/types/diagnostics";
 import { TypeCache } from "./util/types/typeCache";
 import {
   createTypeChecker,
diff --git a/src/forest.ts b/src/forest.ts
index 8949000..afdaadf 100644
--- a/src/forest.ts
+++ b/src/forest.ts
@@ -7,7 +7,7 @@ import { existsSync } from "fs";
 import { URI } from "vscode-uri";
 import { SyntaxNodeMap } from "./util/types/syntaxNodeMap";
 import { SymbolMap } from "./util/types/binder";
-import { Diagnostic } from "vscode-languageserver";
+import { Diagnostic } from "./util/types/diagnostics";
 
 export interface ITreeContainer {
   uri: string;
diff --git a/src/providers/codeActionProvider.ts b/src/providers/codeActionProvider.ts
index b6354aa..4c6531c 100644
--- a/src/providers/codeActionProvider.ts
+++ b/src/providers/codeActionProvider.ts
@@ -9,6 +9,7 @@ import {
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { SyntaxNode, Tree } from "web-tree-sitter";
+import { convertFromAnalyzerDiagnostic } from "./diagnostics/typeInferenceDiagnostics";
 import { IElmWorkspace } from "../elmWorkspace";
 import { ElmWorkspaceMatcher, IParams } from "../util/elmWorkspaceMatcher";
 import { MultiMap } from "../util/multiMap";
@@ -16,6 +17,7 @@ import { RefactorEditUtils } from "../util/refactorEditUtils";
 import { Settings } from "../util/settings";
 import { flatMap, TreeUtils } from "../util/treeUtils";
 import { Diagnostics } from "../util/types/diagnostics";
+import { IDiagnostic } from "./diagnostics/diagnosticsProvider";
 import { ElmLsDiagnostics } from "./diagnostics/elmLsDiagnostics";
 import { ElmMakeDiagnostics } from "./diagnostics/elmMakeDiagnostics";
 import { diagnosticsEquals } from "./diagnostics/fileDiagnostics";
@@ -88,13 +90,14 @@ export class CodeActionProvider {
     params: ICodeActionParams,
     title: string,
     edits: TextEdit[],
+    isPreferred = false,
   ): CodeAction {
     const changes = { [params.sourceFile.uri]: edits };
     return {
       title,
       kind: CodeActionKind.QuickFix,
       edit: { changes },
-      isPreferred: true,
+      isPreferred,
     };
   }
 
@@ -134,45 +137,45 @@ export class CodeActionProvider {
     // handles the diagnostic error code and ask for the code actions for that error
     // and the fix all code action for that error if there are other diagnostics with
     // the same error code
-    params.context.diagnostics.forEach((diagnostic) => {
-      if (typeof diagnostic?.code === "string") {
-        const registrations = CodeActionProvider.errorCodeToRegistrationMap.getAll(
-          diagnostic.code,
-        );
+    (<IDiagnostic[]>params.context.diagnostics).forEach((diagnostic) => {
+      const registrations = CodeActionProvider.errorCodeToRegistrationMap.getAll(
+        diagnostic.data.code,
+      );
 
-        // Set the params range to the diagnostic range so we get the correct nodes
-        params.range = diagnostic.range;
-
-        results.push(
-          ...flatMap(registrations, (reg) => {
-            const codeActions =
-              reg
-                .getCodeActions(params)
-                ?.map((codeAction) =>
-                  this.addDiagnosticToCodeAction(codeAction, diagnostic),
-                ) ?? [];
-
-            if (
-              codeActions.length > 0 &&
-              params.program
-                .getDiagnostics(params.sourceFile)
-                .some(
-                  (diag) =>
-                    !diagnosticsEquals(diag, diagnostic) &&
-                    diag.code === diagnostic.code,
-                )
-            ) {
-              const fixAllCodeAction = reg.getFixAllCodeAction(params);
-
-              if (fixAllCodeAction) {
-                codeActions?.push(fixAllCodeAction);
-              }
+      // Set the params range to the diagnostic range so we get the correct nodes
+      params.range = diagnostic.range;
+
+      results.push(
+        ...flatMap(registrations, (reg) => {
+          const codeActions =
+            reg
+              .getCodeActions(params)
+              ?.map((codeAction) =>
+                this.addDiagnosticToCodeAction(codeAction, diagnostic),
+              ) ?? [];
+
+          if (
+            codeActions.length > 0 &&
+            params.program
+              .getDiagnostics(params.sourceFile)
+              .some(
+                (diag) =>
+                  !diagnosticsEquals(
+                    convertFromAnalyzerDiagnostic(diag),
+                    diagnostic,
+                  ) && diag.code === diagnostic.data.code,
+              )
+          ) {
+            const fixAllCodeAction = reg.getFixAllCodeAction(params);
+
+            if (fixAllCodeAction) {
+              codeActions?.push(fixAllCodeAction);
             }
+          }
 
-            return codeActions;
-          }),
-        );
-      }
+          return codeActions;
+        }),
+      );
     });
 
     return [
diff --git a/src/providers/diagnostics/diagnosticsProvider.ts b/src/providers/diagnostics/diagnosticsProvider.ts
index a7afb90..53b8dfc 100644
--- a/src/providers/diagnostics/diagnosticsProvider.ts
+++ b/src/providers/diagnostics/diagnosticsProvider.ts
@@ -36,6 +36,13 @@ export interface IElmIssue {
   file: string;
 }
 
+export interface IDiagnostic extends Omit<Diagnostic, "code"> {
+  data: {
+    uri: string;
+    code: string;
+  };
+}
+
 class PendingDiagnostics extends Map<string, number> {
   public getOrderedFiles(): string[] {
     return Array.from(this.entries())
@@ -177,7 +184,7 @@ export class DiagnosticsProvider {
     return result;
   }
 
-  public getCurrentDiagnostics(uri: string): Diagnostic[] {
+  public getCurrentDiagnostics(uri: string): IDiagnostic[] {
     return this.currentDiagnostics.get(uri)?.get() ?? [];
   }
 
@@ -242,7 +249,7 @@ export class DiagnosticsProvider {
   private updateDiagnostics(
     uri: string,
     kind: DiagnosticKind,
-    diagnostics: Diagnostic[],
+    diagnostics: IDiagnostic[],
   ): void {
     let didUpdate = false;
 
@@ -377,7 +384,7 @@ export class DiagnosticsProvider {
   }
 
   private resetDiagnostics(
-    diagnosticList: Map<string, Diagnostic[]>,
+    diagnosticList: Map<string, IDiagnostic[]>,
     diagnosticKind: DiagnosticKind,
   ): void {
     this.currentDiagnostics.forEach((fileDiagnostics, diagnosticsUri) => {
diff --git a/src/providers/diagnostics/elmDiagnosticsHelper.ts b/src/providers/diagnostics/elmDiagnosticsHelper.ts
index a746937..b14851d 100644
--- a/src/providers/diagnostics/elmDiagnosticsHelper.ts
+++ b/src/providers/diagnostics/elmDiagnosticsHelper.ts
@@ -1,15 +1,15 @@
 import path from "path";
-import { Diagnostic, DiagnosticSeverity, Range } from "vscode-languageserver";
+import { DiagnosticSeverity, Range } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { Diagnostics } from "../../util/types/diagnostics";
-import { IElmIssue } from "./diagnosticsProvider";
+import { IDiagnostic, IElmIssue } from "./diagnosticsProvider";
 import { NAMING_ERROR } from "./elmMakeDiagnostics";
 
 export class ElmDiagnosticsHelper {
   public static issuesToDiagnosticMap(
     issues: IElmIssue[],
     elmWorkspaceFolder: URI,
-  ): Map<string, Diagnostic[]> {
+  ): Map<string, IDiagnostic[]> {
     return issues.reduce((acc, issue) => {
       const uri = this.getUriFromIssue(issue, elmWorkspaceFolder);
       const diagnostic = this.elmMakeIssueToDiagnostic(issue);
@@ -17,7 +17,7 @@ export class ElmDiagnosticsHelper {
       arr.push(diagnostic);
       acc.set(uri, arr);
       return acc;
-    }, new Map<string, Diagnostic[]>());
+    }, new Map<string, IDiagnostic[]>());
   }
 
   private static severityStringToDiagnosticSeverity(
@@ -42,7 +42,7 @@ export class ElmDiagnosticsHelper {
     ).toString();
   }
 
-  private static elmMakeIssueToDiagnostic(issue: IElmIssue): Diagnostic {
+  private static elmMakeIssueToDiagnostic(issue: IElmIssue): IDiagnostic {
     const lineRange: Range = Range.create(
       issue.region.start.line - 1,
       issue.region.start.column - 1,
@@ -52,18 +52,18 @@ export class ElmDiagnosticsHelper {
 
     const messagePrefix = issue.overview ? `${issue.overview} - ` : "";
 
-    let code = undefined;
+    let code = "elm_make";
 
     if (issue.overview.startsWith(NAMING_ERROR)) {
       code = Diagnostics.MissingValue.code;
     }
 
-    return Diagnostic.create(
-      lineRange,
-      `${messagePrefix}${issue.details.replace(/\[\d+m/g, "")}`,
-      this.severityStringToDiagnosticSeverity(issue.type),
-      code,
-      "Elm",
-    );
+    return {
+      range: lineRange,
+      message: `${messagePrefix}${issue.details.replace(/\[\d+m/g, "")}`,
+      severity: this.severityStringToDiagnosticSeverity(issue.type),
+      source: "Elm",
+      data: { uri: issue.file, code },
+    };
   }
 }
diff --git a/src/providers/diagnostics/elmLsDiagnostics.ts b/src/providers/diagnostics/elmLsDiagnostics.ts
index a0e72b4..13f25ef 100644
--- a/src/providers/diagnostics/elmLsDiagnostics.ts
+++ b/src/providers/diagnostics/elmLsDiagnostics.ts
@@ -5,7 +5,6 @@ import {
   CodeAction,
   CodeActionKind,
   CodeActionParams,
-  Diagnostic,
   DiagnosticSeverity,
   DiagnosticTag,
   Range,
@@ -19,6 +18,7 @@ import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
 import { Utils } from "../../util/utils";
+import { IDiagnostic } from "./diagnosticsProvider";
 
 export class ElmLsDiagnostics {
   ELMLS = "ElmLS";
@@ -331,7 +331,7 @@ export class ElmLsDiagnostics {
   public createDiagnostics = (
     treeContainer: ITreeContainer,
     elmWorkspace: IElmWorkspace,
-  ): Diagnostic[] => {
+  ): IDiagnostic[] => {
     const tree = treeContainer.tree;
     const uri = treeContainer.uri;
     try {
@@ -360,15 +360,15 @@ export class ElmLsDiagnostics {
 
   public onCodeAction(params: CodeActionParams): CodeAction[] {
     const { uri } = params.textDocument;
-    const elmDiagnostics: Diagnostic[] = params.context.diagnostics.filter(
+    const elmDiagnostics: IDiagnostic[] = params.context.diagnostics.filter(
       (diagnostic) => diagnostic.source === this.ELMLS,
-    );
+    ) as IDiagnostic[];
 
     return this.convertDiagnosticsToCodeActions(elmDiagnostics, uri);
   }
 
   private convertDiagnosticsToCodeActions(
-    diagnostics: Diagnostic[],
+    diagnostics: IDiagnostic[],
     uri: string,
   ): CodeAction[] {
     const result: CodeAction[] = [];
@@ -381,7 +381,7 @@ export class ElmLsDiagnostics {
 
     if (treeContainer) {
       diagnostics.forEach((diagnostic) => {
-        if (diagnostic.code === "unused_imported_value") {
+        if (diagnostic.data.code === "unused_imported_value") {
           const node = TreeUtils.getNamedDescendantForPosition(
             treeContainer.tree.rootNode,
             diagnostic.range.start,
@@ -427,7 +427,7 @@ export class ElmLsDiagnostics {
           }
         }
 
-        if (diagnostic.code === "unused_alias") {
+        if (diagnostic.data.code === "unused_alias") {
           const node = TreeUtils.getNamedDescendantForPosition(
             treeContainer.tree.rootNode,
             diagnostic.range.end,
@@ -443,7 +443,7 @@ export class ElmLsDiagnostics {
           });
         }
 
-        if (diagnostic.code === "unused_pattern") {
+        if (diagnostic.data.code === "unused_pattern") {
           const node = TreeUtils.getNamedDescendantForPosition(
             treeContainer.tree.rootNode,
             diagnostic.range.start,
@@ -468,8 +468,8 @@ export class ElmLsDiagnostics {
     return result;
   }
 
-  private getUnusedImportDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedImportDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const moduleImports = this.moduleImportsQuery
       .matches(tree.rootNode)
@@ -495,12 +495,12 @@ export class ElmLsDiagnostics {
 
       if (references.length === 0 && moduleImport.parent) {
         diagnostics.push({
-          code: "unused_import",
           range: this.getNodeRange(moduleImport.parent),
           message: `Unused import \`${moduleImport.text}\``,
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
           tags: [DiagnosticTag.Unnecessary],
+          data: { uri: tree.uri, code: "unused_import" },
         });
       }
     });
@@ -508,8 +508,8 @@ export class ElmLsDiagnostics {
     return diagnostics;
   }
 
-  private getUnusedImportValueAndTypeDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedImportValueAndTypeDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const exposedValuesAndTypes = this.exposedValuesAndTypesQuery
       .matches(tree.rootNode)
@@ -541,7 +541,6 @@ export class ElmLsDiagnostics {
 
       if (references.length === 0) {
         diagnostics.push({
-          code: "unused_imported_value",
           range: this.getNodeRange(exposedValueOrType),
           message: `Unused imported ${
             exposedValueOrType.type === "exposed_type" ? "type" : "value"
@@ -549,6 +548,7 @@ export class ElmLsDiagnostics {
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
           tags: [DiagnosticTag.Unnecessary],
+          data: { uri: tree.uri, code: "unused_imported_value" },
         });
       }
     });
@@ -556,8 +556,8 @@ export class ElmLsDiagnostics {
     return diagnostics;
   }
 
-  private getUnusedImportAliasDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedImportAliasDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const moduleAliases = this.importModuleAliasesQuery
       .matches(tree.rootNode)
@@ -573,12 +573,12 @@ export class ElmLsDiagnostics {
 
       if (references.length === 0 && moduleAlias.parent) {
         diagnostics.push({
-          code: "unused_alias",
           range: this.getNodeRange(moduleAlias.parent),
           message: `Unused import alias \`${moduleAlias.text}\``,
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
           tags: [DiagnosticTag.Unnecessary],
+          data: { uri: tree.uri, code: "unused_alias" },
         });
       }
     });
@@ -586,8 +586,8 @@ export class ElmLsDiagnostics {
     return diagnostics;
   }
 
-  private getUnusedPatternVariableDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedPatternVariableDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const patternMatches = this.patternsQuery.matches(tree.rootNode);
 
@@ -654,23 +654,23 @@ export class ElmLsDiagnostics {
 
           if (!outsideRef) {
             diagnostics.push({
-              code: "unused_top_level",
               range: this.getNodeRange(pattern),
               message: `Unused top level definition \`${pattern.text}\``,
               severity: DiagnosticSeverity.Warning,
               source: this.ELMLS,
               tags: [DiagnosticTag.Unnecessary],
+              data: { uri: tree.uri, code: "unused_top_level" },
             });
           }
         } else if (references.length === 0) {
           {
             diagnostics.push({
-              code: "unused_pattern",
               range: this.getNodeRange(pattern),
               message: `Unused pattern variable \`${pattern.text}\``,
               severity: DiagnosticSeverity.Warning,
               source: this.ELMLS,
               tags: [DiagnosticTag.Unnecessary],
+              data: { uri: tree.uri, code: "unused_pattern" },
             });
           }
         }
@@ -681,8 +681,8 @@ export class ElmLsDiagnostics {
 
   private getCaseBranchMapNothingToNothingDiagnostics(
     tree: Tree,
-  ): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  ): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const caseBranches = this.caseBranchesQuery
       .matches(tree.rootNode)
@@ -690,19 +690,19 @@ export class ElmLsDiagnostics {
 
     caseBranches.forEach((caseBranch) => {
       diagnostics.push({
-        code: "map_nothing_to_nothing",
         range: this.getNodeRange(caseBranch),
         message: `\`Nothing\` mapped to \`Nothing\` in case expression. Use Maybe.map or Maybe.andThen instead.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "map_nothing_to_nothing" },
       });
     });
 
     return diagnostics;
   }
 
-  private getBooleanCaseExpressionDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getBooleanCaseExpressionDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     // For some reason, we can't match on case_expr, tree-sitter throws a memory access error
     const caseExpressions = this.booleanCaseExpressionsQuery
@@ -712,19 +712,19 @@ export class ElmLsDiagnostics {
 
     caseExpressions.forEach((caseExpr) => {
       diagnostics.push({
-        code: "boolean_case_expr",
         range: this.getNodeRange(caseExpr),
         message: `Use an if expression instead of a case expression.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "boolean_case_expr" },
       });
     });
 
     return diagnostics;
   }
 
-  private getDropConcatOfListsDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getDropConcatOfListsDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const listExpressions = this.concatOfListsQuery
       .matches(tree.rootNode)
@@ -733,7 +733,6 @@ export class ElmLsDiagnostics {
 
     listExpressions.forEach(([startList, endList]) => {
       diagnostics.push({
-        code: "drop_concat_of_lists",
         range: {
           start: this.getNodeRange(startList).start,
           end: this.getNodeRange(endList).end,
@@ -741,14 +740,15 @@ export class ElmLsDiagnostics {
         message: `If you concatenate two lists, then you can merge them into one list.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "drop_concat_of_lists" },
       });
     });
 
     return diagnostics;
   }
 
-  private getDropConsOfItemAndListDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getDropConsOfItemAndListDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const consExpressions = this.consOfItemAndListQuery
       .matches(tree.rootNode)
@@ -757,7 +757,6 @@ export class ElmLsDiagnostics {
 
     consExpressions.forEach(([itemExpr, listExpr]) => {
       diagnostics.push({
-        code: "drop_cons_of_item_and_list",
         range: {
           start: this.getNodeRange(itemExpr).start,
           end: this.getNodeRange(listExpr).end,
@@ -765,14 +764,15 @@ export class ElmLsDiagnostics {
         message: `If you cons an item to a literal list, then you can just put the item into the list.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "drop_cons_of_item_and_list" },
       });
     });
 
     return diagnostics;
   }
 
-  private getUseConsOverConcatDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUseConsOverConcatDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const concatExpressions = this.useConsOverConcatQuery
       .matches(tree.rootNode)
@@ -781,7 +781,6 @@ export class ElmLsDiagnostics {
 
     concatExpressions.forEach(([firstPart, lastPart]) => {
       diagnostics.push({
-        code: "use_cons_over_concat",
         range: {
           start: this.getNodeRange(firstPart).start,
           end: this.getNodeRange(lastPart).end,
@@ -789,6 +788,7 @@ export class ElmLsDiagnostics {
         message: `If you concatenate two lists, but the first item is a single element list, then you should use the cons operator.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "use_cons_over_concat" },
       });
     });
 
@@ -799,8 +799,8 @@ export class ElmLsDiagnostics {
     tree: Tree,
     uri: string,
     elmWorkspace: IElmWorkspace,
-  ): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  ): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const recordTypes = this.singleFieldRecordTypesQuery
       .matches(tree.rootNode)
@@ -827,11 +827,11 @@ export class ElmLsDiagnostics {
 
       if (isSingleField) {
         diagnostics.push({
-          code: "single_field_record",
           range: this.getNodeRange(recordType),
           message: `Using a record is obsolete if you only plan to store a single field in it.`,
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
+          data: { uri: tree.uri, code: "single_field_record" },
         });
       }
     });
@@ -839,8 +839,8 @@ export class ElmLsDiagnostics {
     return diagnostics;
   }
 
-  private getUnnecessaryListConcatDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnnecessaryListConcatDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const listConcats = this.unnecessaryListConcatQuery
       .matches(tree.rootNode)
@@ -849,19 +849,19 @@ export class ElmLsDiagnostics {
 
     listConcats.forEach((listConcat) => {
       diagnostics.push({
-        code: "unnecessary_list_concat",
         range: this.getNodeRange(listConcat),
         message: `You should just merge the arguments of \`List.concat\` to a single list.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "unnecessary_list_concat" },
       });
     });
 
     return diagnostics;
   }
 
-  private getUnnecessaryPortModuleDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnnecessaryPortModuleDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const unusedPortMatches = this.unusedPortModuleQuery.matches(tree.rootNode);
 
@@ -870,19 +870,21 @@ export class ElmLsDiagnostics {
       !unusedPortMatches[1]
     ) {
       diagnostics.push({
-        code: "unnecessary_port_module",
         range: this.getNodeRange(unusedPortMatches[0].captures[0].node),
         message: `Module is definined as a \`port\` module, but does not define any ports.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "unnecessary_port_module" },
       });
     }
 
     return diagnostics;
   }
 
-  private getFullyAppliedOperatorAsPrefixDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getFullyAppliedOperatorAsPrefixDiagnostics(
+    tree: Tree,
+  ): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const operatorFunctions = this.operatorFunctionsQuery
       .matches(tree.rootNode)
@@ -891,19 +893,19 @@ export class ElmLsDiagnostics {
 
     operatorFunctions.forEach((operatorFunction) => {
       diagnostics.push({
-        code: "no_uncurried_prefix",
         range: this.getNodeRange(operatorFunction),
         message: `Don't use fully applied prefix notation for operators.`,
         severity: DiagnosticSeverity.Warning,
         source: this.ELMLS,
+        data: { uri: tree.uri, code: "no_uncurried_prefix" },
       });
     });
 
     return diagnostics;
   }
 
-  private getUnusedTypeAliasDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedTypeAliasDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const typeAliases = this.typeAliasesQuery
       .matches(tree.rootNode)
@@ -932,12 +934,12 @@ export class ElmLsDiagnostics {
 
       if (references.length === 0 && typeAlias.parent) {
         diagnostics.push({
-          code: "unused_type_alias",
           range: this.getNodeRange(typeAlias.parent),
           message: `Type alias \`${typeAlias.text}\` is not used.`,
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
           tags: [DiagnosticTag.Unnecessary],
+          data: { uri: tree.uri, code: "unused_type_alias" },
         });
       }
     });
@@ -945,8 +947,8 @@ export class ElmLsDiagnostics {
     return diagnostics;
   }
 
-  private getUnusedValueConstructorDiagnostics(tree: Tree): Diagnostic[] {
-    const diagnostics: Diagnostic[] = [];
+  private getUnusedValueConstructorDiagnostics(tree: Tree): IDiagnostic[] {
+    const diagnostics: IDiagnostic[] = [];
 
     const unionVariants = this.unionVariantsQuery
       .matches(tree.rootNode)
@@ -980,12 +982,12 @@ export class ElmLsDiagnostics {
 
       if (references.length === 0 && unionVariant.parent) {
         diagnostics.push({
-          code: "unused_value_constructor",
           range: this.getNodeRange(unionVariant.parent),
           message: `Value constructor \`${unionVariant.text}\` is not used.`,
           severity: DiagnosticSeverity.Warning,
           source: this.ELMLS,
           tags: [DiagnosticTag.Unnecessary],
+          data: { uri: tree.uri, code: "unused_value_constructor" },
         });
       }
     });
diff --git a/src/providers/diagnostics/elmMakeDiagnostics.ts b/src/providers/diagnostics/elmMakeDiagnostics.ts
index 67774d3..1960423 100644
--- a/src/providers/diagnostics/elmMakeDiagnostics.ts
+++ b/src/providers/diagnostics/elmMakeDiagnostics.ts
@@ -8,7 +8,6 @@ import {
   CodeAction,
   CodeActionKind,
   CodeActionParams,
-  Diagnostic,
   Connection,
   TextEdit,
 } from "vscode-languageserver";
@@ -22,7 +21,7 @@ import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { Settings } from "../../util/settings";
 import { TreeUtils } from "../../util/treeUtils";
 import { Utils } from "../../util/utils";
-import { IElmIssue } from "./diagnosticsProvider";
+import { IDiagnostic, IElmIssue } from "./diagnosticsProvider";
 import { ElmDiagnosticsHelper } from "./elmDiagnosticsHelper";
 import execa = require("execa");
 import { IElmWorkspace } from "../../elmWorkspace";
@@ -106,10 +105,10 @@ export class ElmMakeDiagnostics {
   private elmWorkspaceMatcher: ElmWorkspaceMatcher<URI>;
   private neededImports: Map<
     string,
-    { moduleName: string; valueName?: string; diagnostic: Diagnostic }[]
+    { moduleName: string; valueName?: string; diagnostic: IDiagnostic }[]
   > = new Map<
     string,
-    { moduleName: string; valueName?: string; diagnostic: Diagnostic }[]
+    { moduleName: string; valueName?: string; diagnostic: IDiagnostic }[]
   >();
   private settings: Settings;
   private connection: Connection;
@@ -122,7 +121,7 @@ export class ElmMakeDiagnostics {
 
   public createDiagnostics = async (
     filePath: URI,
-  ): Promise<Map<string, Diagnostic[]>> => {
+  ): Promise<Map<string, IDiagnostic[]>> => {
     const workspaceRootPath = this.elmWorkspaceMatcher
       .getProgramFor(filePath)
       .getRootPath();
@@ -200,15 +199,15 @@ export class ElmMakeDiagnostics {
 
   public onCodeAction(params: CodeActionParams): CodeAction[] {
     const { uri } = params.textDocument;
-    const elmMakeDiagnostics: Diagnostic[] = this.filterElmMakeDiagnostics(
-      params.context.diagnostics,
+    const elmMakeDiagnostics: IDiagnostic[] = this.filterElmMakeDiagnostics(
+      params.context.diagnostics as IDiagnostic[],
     );
 
     return this.convertDiagnosticsToCodeActions(elmMakeDiagnostics, uri);
   }
 
   private convertDiagnosticsToCodeActions(
-    diagnostics: Diagnostic[],
+    diagnostics: IDiagnostic[],
     uri: string,
   ): CodeAction[] {
     const result: CodeAction[] = [];
@@ -295,7 +294,7 @@ export class ElmMakeDiagnostics {
 
   private addCaseQuickfixes(
     sourceTree: ITreeContainer | undefined,
-    diagnostic: Diagnostic,
+    diagnostic: IDiagnostic,
     uri: string,
     elmWorkspace: IElmWorkspace,
   ): CodeAction[] {
@@ -393,7 +392,7 @@ export class ElmMakeDiagnostics {
   private createCaseQuickFix(
     uri: string,
     replaceWith: string,
-    diagnostic: Diagnostic,
+    diagnostic: IDiagnostic,
     title: string,
   ): CodeAction {
     const map: {
@@ -414,7 +413,7 @@ export class ElmMakeDiagnostics {
   private createQuickFix(
     uri: string,
     replaceWith: string,
-    diagnostic: Diagnostic,
+    diagnostic: IDiagnostic,
     title: string,
   ): CodeAction {
     const map: {
@@ -434,7 +433,7 @@ export class ElmMakeDiagnostics {
 
   private createImportQuickFix(
     uri: string,
-    diagnostic: Diagnostic,
+    diagnostic: IDiagnostic,
     moduleName: string,
     nameToImport?: string,
   ): CodeAction {
@@ -469,7 +468,7 @@ export class ElmMakeDiagnostics {
     };
   }
 
-  private filterElmMakeDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
+  private filterElmMakeDiagnostics(diagnostics: IDiagnostic[]): IDiagnostic[] {
     return diagnostics.filter((diagnostic) => diagnostic.source === ELM_MAKE);
   }
 
diff --git a/src/providers/diagnostics/fileDiagnostics.ts b/src/providers/diagnostics/fileDiagnostics.ts
index 6c2a44e..a7facfa 100644
--- a/src/providers/diagnostics/fileDiagnostics.ts
+++ b/src/providers/diagnostics/fileDiagnostics.ts
@@ -1,5 +1,5 @@
-import { Diagnostic } from "vscode-languageserver";
 import { Utils } from "../../util/utils";
+import { IDiagnostic } from "./diagnosticsProvider";
 
 export const enum DiagnosticKind {
   ElmMake,
@@ -8,16 +8,17 @@ export const enum DiagnosticKind {
   ElmLS,
 }
 
-export function diagnosticsEquals(a: Diagnostic, b: Diagnostic): boolean {
+export function diagnosticsEquals(a: IDiagnostic, b: IDiagnostic): boolean {
   if (a === b) {
     return true;
   }
 
   return (
-    a.code === b.code &&
     a.message === b.message &&
     a.severity === b.severity &&
     a.source === b.source &&
+    a.data.code === b.data.code &&
+    a.data.uri === b.data.uri &&
     Utils.rangeEquals(a.range, b.range) &&
     Utils.arrayEquals(
       a.relatedInformation ?? [],
@@ -35,14 +36,14 @@ export function diagnosticsEquals(a: Diagnostic, b: Diagnostic): boolean {
 }
 
 export class FileDiagnostics {
-  private diagnostics: Map<DiagnosticKind, Diagnostic[]> = new Map<
+  private diagnostics: Map<DiagnosticKind, IDiagnostic[]> = new Map<
     DiagnosticKind,
-    Diagnostic[]
+    IDiagnostic[]
   >();
 
   constructor(public uri: string) {}
 
-  public get(): Diagnostic[] {
+  public get(): IDiagnostic[] {
     return [
       ...this.getForKind(DiagnosticKind.ElmMake),
       ...this.getForKind(DiagnosticKind.ElmTest),
@@ -51,7 +52,7 @@ export class FileDiagnostics {
     ];
   }
 
-  public update(kind: DiagnosticKind, diagnostics: Diagnostic[]): boolean {
+  public update(kind: DiagnosticKind, diagnostics: IDiagnostic[]): boolean {
     const existing = this.getForKind(kind);
     if (Utils.arrayEquals(existing, diagnostics, diagnosticsEquals)) {
       return false;
@@ -61,7 +62,7 @@ export class FileDiagnostics {
     return true;
   }
 
-  public getForKind(kind: DiagnosticKind): Diagnostic[] {
+  public getForKind(kind: DiagnosticKind): IDiagnostic[] {
     return this.diagnostics.get(kind) ?? [];
   }
 }
diff --git a/src/providers/diagnostics/typeInferenceDiagnostics.ts b/src/providers/diagnostics/typeInferenceDiagnostics.ts
index 36cede4..d98c645 100644
--- a/src/providers/diagnostics/typeInferenceDiagnostics.ts
+++ b/src/providers/diagnostics/typeInferenceDiagnostics.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-use-before-define */
-import { CancellationToken, Diagnostic } from "vscode-languageserver";
+import { CancellationToken } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
 import { TreeUtils } from "../../util/treeUtils";
 import { mapSyntaxNodeToExpression } from "../../util/types/expressionTree";
@@ -7,7 +7,21 @@ import { TypeChecker } from "../../util/types/typeChecker";
 import { ITreeContainer } from "../../forest";
 import { IElmWorkspace } from "../../elmWorkspace";
 import { ServerCancellationToken } from "../../cancellation";
-import { Diagnostics, error } from "../../util/types/diagnostics";
+import { Diagnostic, Diagnostics, error } from "../../util/types/diagnostics";
+import { IDiagnostic } from "./diagnosticsProvider";
+
+export function convertFromAnalyzerDiagnostic(diag: Diagnostic): IDiagnostic {
+  return {
+    message: diag.message,
+    source: diag.source,
+    severity: diag.severity,
+    range: diag.range,
+    data: {
+      uri: diag.uri,
+      code: diag.code,
+    },
+  };
+}
 
 export class TypeInferenceDiagnostics {
   TYPE_INFERENCE = "Type Inference";
@@ -16,12 +30,14 @@ export class TypeInferenceDiagnostics {
     treeContainer: ITreeContainer,
     program: IElmWorkspace,
     cancellationToken: CancellationToken,
-  ): Promise<Diagnostic[]> {
+  ): Promise<IDiagnostic[]> {
     const checker = program.getTypeChecker();
 
-    const diagnostics: Diagnostic[] = [];
+    const diagnostics: IDiagnostic[] = [];
 
-    diagnostics.push(...treeContainer.parseDiagnostics);
+    diagnostics.push(
+      ...treeContainer.parseDiagnostics.map(convertFromAnalyzerDiagnostic),
+    );
 
     const allTopLevelFunctions =
       TreeUtils.findAllTopLevelFunctionDeclarations(treeContainer.tree) ?? [];
@@ -33,7 +49,7 @@ export class TypeInferenceDiagnostics {
           new ServerCancellationToken(cancellationToken),
         )
         .then((diag) => {
-          diagnostics.push(...diag);
+          diagnostics.push(...diag.map(convertFromAnalyzerDiagnostic));
 
           // Get other custom diagnostics
           allTopLevelFunctions.forEach((declaration) => {
@@ -56,13 +72,13 @@ export class TypeInferenceDiagnostics {
     checker: TypeChecker,
     declaration: SyntaxNode,
     treeContainer: ITreeContainer,
-  ): Diagnostic[] {
+  ): IDiagnostic[] {
     const valueDeclaration = mapSyntaxNodeToExpression(declaration);
     if (valueDeclaration?.nodeType !== "ValueDeclaration") {
       return [];
     }
 
-    const diagnostics: Diagnostic[] = [];
+    const diagnostics: IDiagnostic[] = [];
 
     if (!valueDeclaration.typeAnnotation) {
       const typeString: string = checker.typeToString(
@@ -76,10 +92,12 @@ export class TypeInferenceDiagnostics {
         declaration.firstNamedChild?.firstNamedChild
       ) {
         diagnostics.push(
-          error(
-            declaration.firstNamedChild.firstNamedChild,
-            Diagnostics.MissingTypeAnnotation,
-            typeString,
+          convertFromAnalyzerDiagnostic(
+            error(
+              declaration.firstNamedChild.firstNamedChild,
+              Diagnostics.MissingTypeAnnotation,
+              typeString,
+            ),
           ),
         );
       }
diff --git a/src/util/types/diagnostics.ts b/src/util/types/diagnostics.ts
index f7c51f9..89a9c0e 100644
--- a/src/util/types/diagnostics.ts
+++ b/src/util/types/diagnostics.ts
@@ -1,7 +1,17 @@
-import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
+/* eslint-disable @typescript-eslint/naming-convention */
+import { DiagnosticSeverity, Range } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
 import { PositionUtil } from "../../positionUtil";
 
+export interface Diagnostic {
+  code: string;
+  message: string;
+  source: string;
+  severity: DiagnosticSeverity;
+  range: Range;
+  uri: string;
+}
+
 function format(text: string, ...args: (string | number)[]): string {
   return text.replace(/{(\d+)}/g, (_match, index: string) => `${args[+index]}`);
 }
@@ -23,7 +33,7 @@ export function errorWithEndNode(
     code: diagnostic.code,
     severity: diagnostic.severity,
     source: "elm",
-    data: { uri: node.tree.uri },
+    uri: node.tree.uri,
   };
 }
 
diff --git a/src/util/types/typeChecker.ts b/src/util/types/typeChecker.ts
index 4b20711..00aa672 100644
--- a/src/util/types/typeChecker.ts
+++ b/src/util/types/typeChecker.ts
@@ -9,7 +9,7 @@ import {
 } from "./expressionTree";
 import { IElmWorkspace } from "../../elmWorkspace";
 import { container } from "tsyringe";
-import { Connection, Diagnostic } from "vscode-languageserver";
+import { Connection } from "vscode-languageserver";
 import {
   Type,
   TUnknown,
@@ -25,7 +25,7 @@ import { Sequence } from "../sequence";
 import { Utils } from "../utils";
 import { TypeExpression } from "./typeExpression";
 import { ICancellationToken } from "../../cancellation";
-import { Diagnostics, error } from "./diagnostics";
+import { Diagnostic, Diagnostics, error } from "./diagnostics";
 
 export let bindTime = 0;
 export function resetBindTime(): void {
@@ -40,13 +40,11 @@ export interface DefinitionResult {
 
 class DiagnosticsCollection extends Map<string, Diagnostic[]> {
   public add(diagnostic: Diagnostic): void {
-    const uri = (<any>diagnostic.data).uri;
-
-    let diagnostics = super.get(uri);
+    let diagnostics = super.get(diagnostic.uri);
 
     if (!diagnostics) {
       diagnostics = [];
-      super.set(uri, diagnostics);
+      super.set(diagnostic.uri, diagnostics);
     }
 
     diagnostics.push(diagnostic);
diff --git a/src/util/types/typeExpression.ts b/src/util/types/typeExpression.ts
index 8057dd9..8105a06 100644
--- a/src/util/types/typeExpression.ts
+++ b/src/util/types/typeExpression.ts
@@ -35,8 +35,7 @@ import { SyntaxNodeMap } from "./syntaxNodeMap";
 import { Utils } from "../utils";
 import { RecordFieldReferenceTable } from "./recordFieldReferenceTable";
 import { IElmWorkspace } from "../../elmWorkspace";
-import { Diagnostic } from "vscode-languageserver";
-import { Diagnostics, error } from "./diagnostics";
+import { Diagnostic, Diagnostics, error } from "./diagnostics";
 
 export class TypeExpression {
   // All the type variables we've seen
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index aaf6518..53cb42c 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -46,8 +46,12 @@ import { RecordFieldReferenceTable } from "./recordFieldReferenceTable";
 import { TypeChecker } from "./typeChecker";
 import { performance } from "perf_hooks";
 import { ICancellationToken } from "../../cancellation";
-import { Diagnostics, error, errorWithEndNode } from "./diagnostics";
-import { Diagnostic } from "vscode-languageserver";
+import {
+  Diagnostic,
+  Diagnostics,
+  error,
+  errorWithEndNode,
+} from "./diagnostics";
 
 export let inferTime = 0;
 export function resetInferTime(): void {


Test patch:
diff --git a/test/codeActionProvider.test.ts b/test/codeActionProvider.test.ts
index 1572c0d..940e0e1 100644
--- a/test/codeActionProvider.test.ts
+++ b/test/codeActionProvider.test.ts
@@ -2,7 +2,11 @@ import { container } from "tsyringe";
 import { CodeAction } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { IElmWorkspace } from "../src/elmWorkspace";
-import { CodeActionProvider, ICodeActionParams } from "../src/providers";
+import {
+  CodeActionProvider,
+  convertFromAnalyzerDiagnostic,
+  ICodeActionParams,
+} from "../src/providers";
 import { Utils } from "../src/util/utils";
 import { baseUri } from "./utils/mockElmWorkspace";
 import { getTargetPositionFromSource } from "./utils/sourceParser";
@@ -90,7 +94,8 @@ describe("test codeActionProvider", () => {
         context: {
           diagnostics: program
             .getDiagnostics(sourceFile)
-            .filter((diag) => Utils.rangeOverlaps(diag.range, range)),
+            .filter((diag) => Utils.rangeOverlaps(diag.range, range))
+            .map(convertFromAnalyzerDiagnostic),
         },
       }) ?? [];
 
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index 8d2cd2d..0363348 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -1,10 +1,11 @@
 import { debug } from "console";
-import { Diagnostic } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { SyntaxNode } from "web-tree-sitter";
+import { convertFromAnalyzerDiagnostic } from "../../src/providers";
 import { diagnosticsEquals } from "../../src/providers/diagnostics/fileDiagnostics";
 import { TreeUtils } from "../../src/util/treeUtils";
 import {
+  Diagnostic,
   Diagnostics,
   error,
   IDiagnosticMessage,
@@ -93,11 +94,13 @@ describe("test elm diagnostics", () => {
     }
 
     const expected = expectedDiagnostics.map((exp) =>
-      error(nodeAtPosition, exp.message, ...exp.args),
+      convertFromAnalyzerDiagnostic(
+        error(nodeAtPosition, exp.message, ...exp.args),
+      ),
     );
 
     const diagnosticsEqual = Utils.arrayEquals(
-      diagnostics,
+      diagnostics.map(convertFromAnalyzerDiagnostic),
       expected,
       diagnosticsEquals,
     );
diff --git a/test/diagnosticTests/elmLsDiagnostics.test.ts b/test/diagnosticTests/elmLsDiagnostics.test.ts
index b78d6cc..a34ca75 100644
--- a/test/diagnosticTests/elmLsDiagnostics.test.ts
+++ b/test/diagnosticTests/elmLsDiagnostics.test.ts
@@ -1,10 +1,10 @@
 import {
-  Diagnostic,
   DiagnosticSeverity,
   DiagnosticTag,
   Range,
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
+import { IDiagnostic } from "../../src/providers/diagnostics/diagnosticsProvider";
 import { ElmLsDiagnostics } from "../../src/providers/diagnostics/elmLsDiagnostics";
 import { diagnosticsEquals } from "../../src/providers/diagnostics/fileDiagnostics";
 import { Utils } from "../../src/util/utils";
@@ -17,17 +17,18 @@ describe("ElmLsDiagnostics", () => {
   const treeParser = new SourceTreeParser();
 
   const debug = process.argv.find((arg) => arg === "--debug");
+  const uri = URI.file(baseUri + "Main.elm").toString();
 
   async function testDiagnostics(
     source: string,
     code: string,
-    expectedDiagnostics: Diagnostic[],
+    expectedDiagnostics: IDiagnostic[],
   ) {
     await treeParser.init();
     elmDiagnostics = new ElmLsDiagnostics();
 
     const workspace = treeParser.getWorkspace(getSourceFiles(source));
-    const uri = URI.file(baseUri + "Main.elm").toString();
+
     const treeContainer = workspace.getForest().getByUri(uri);
 
     if (!treeContainer) {
@@ -36,7 +37,7 @@ describe("ElmLsDiagnostics", () => {
 
     const diagnostics = elmDiagnostics
       .createDiagnostics(treeContainer, workspace)
-      .filter((diagnostic) => diagnostic.code === code);
+      .filter((diagnostic) => diagnostic.data.code === code);
 
     const diagnosticsEqual = Utils.arrayEquals(
       diagnostics,
@@ -56,13 +57,16 @@ describe("ElmLsDiagnostics", () => {
   }
 
   describe("boolean case expressions", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "boolean_case_expr",
         message: "Use an if expression instead of a case expression.",
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "boolean_case_expr",
+        },
       };
     };
 
@@ -123,14 +127,17 @@ foo x =
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_top_level",
         message: `Unused top level definition \`${name}\``,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_top_level",
+        },
       };
     };
 
@@ -296,14 +303,17 @@ getItemAtIndex index =
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_import",
         message: `Unused import \`${name}\``,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_import",
+        },
       };
     };
 
@@ -408,14 +418,17 @@ foo = 1
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_alias",
         message: `Unused import alias \`${name}\``,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_alias",
+        },
       };
     };
 
@@ -510,14 +523,17 @@ type alias Thing = { name : B.Name }
       range: Range,
       name: string,
       type: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_imported_value",
         message: `Unused imported ${type} \`${name}\``,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_imported_value",
+        },
       };
     };
 
@@ -702,14 +718,17 @@ x y =
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_pattern",
         message: `Unused pattern variable \`${name}\``,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_pattern",
+        },
       };
     };
 
@@ -876,13 +895,16 @@ x =
   });
 
   describe("drop cons of item and list", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "drop_cons_of_item_and_list",
         message: `If you cons an item to a literal list, then you can just put the item into the list.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "drop_cons_of_item_and_list",
+        },
       };
     };
 
@@ -917,13 +939,16 @@ foo =
   });
 
   describe("map nothing to nothing", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "map_nothing_to_nothing",
         message: `\`Nothing\` mapped to \`Nothing\` in case expression. Use Maybe.map or Maybe.andThen instead.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "map_nothing_to_nothing",
+        },
       };
     };
 
@@ -970,13 +995,16 @@ y = case x of
   });
 
   describe("drop concat of lists", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "drop_concat_of_lists",
         message: `If you concatenate two lists, then you can merge them into one list.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "drop_concat_of_lists",
+        },
       };
     };
 
@@ -1028,13 +1056,16 @@ foo =
   });
 
   describe("use cons over concat", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "use_cons_over_concat",
         message: `If you concatenate two lists, but the first item is a single element list, then you should use the cons operator.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "use_cons_over_concat",
+        },
       };
     };
 
@@ -1069,13 +1100,16 @@ foo =
   });
 
   describe("single field record", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "single_field_record",
         message: `Using a record is obsolete if you only plan to store a single field in it.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "single_field_record",
+        },
       };
     };
 
@@ -1173,13 +1207,16 @@ type alias CheckboxParams a =
   });
 
   describe("unnecessary list concat", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "unnecessary_list_concat",
         message: `You should just merge the arguments of \`List.concat\` to a single list.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "unnecessary_list_concat",
+        },
       };
     };
 
@@ -1239,13 +1276,16 @@ foo =
   });
 
   describe("unnecessary port module", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "unnecessary_port_module",
         message: `Module is definined as a \`port\` module, but does not define any ports.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "unnecessary_port_module",
+        },
       };
     };
 
@@ -1288,13 +1328,16 @@ port foo : String -> Cmd msg
   });
 
   describe("fully applied operator as prefix", () => {
-    const diagnosticWithRange = (range: Range): Diagnostic => {
+    const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        code: "no_uncurried_prefix",
         message: `Don't use fully applied prefix notation for operators.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
+        data: {
+          uri,
+          code: "no_uncurried_prefix",
+        },
       };
     };
 
@@ -1328,14 +1371,17 @@ foo = (+) 1
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_type_alias",
         message: `Type alias \`${name}\` is not used.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_type_alias",
+        },
       };
     };
 
@@ -1440,14 +1486,17 @@ foo = 1
     const diagnosticWithRangeAndName = (
       range: Range,
       name: string,
-    ): Diagnostic => {
+    ): IDiagnostic => {
       return {
-        code: "unused_value_constructor",
         message: `Value constructor \`${name}\` is not used.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
         tags: [DiagnosticTag.Unnecessary],
+        data: {
+          uri,
+          code: "unused_value_constructor",
+        },
       };
     };
 
diff --git a/test/utils/mockElmWorkspace.ts b/test/utils/mockElmWorkspace.ts
index 09b2d71..8b65e49 100644
--- a/test/utils/mockElmWorkspace.ts
+++ b/test/utils/mockElmWorkspace.ts
@@ -16,8 +16,8 @@ import {
   IPossibleImportsCache,
   PossibleImportsCache,
 } from "../../src/util/possibleImportsCache";
-import { Diagnostic } from "vscode-languageserver";
 import { ICancellationToken } from "../../src/cancellation";
+import { Diagnostic } from "../../src/util/types/diagnostics";
 
 export const baseUri = Path.join(__dirname, "../sources/src/");