Repository: elm-tooling/elm-language-server
Pull request #418 (2020-12-22 13:21:38)

Start work on 2.0.0 changelog
Since there are a lot of changes, I tried to break them up in sections and add more description. 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23d69ac..71d4856 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
 # Change Log
 
+## 2.0.0
+
+### Features
+
+- Enabled type checking diagnostics that update on document change. These include parsing errors, type mismatch errors, value not found errors, etc
+- Support file events APIs (Previously VSCode only)
+
+  - On file create, the module name will be inserted
+  - On file rename, the module name and all references will be updated
+
+- A new "extract function" code action. Select a complete expression and it can be extracted to a function in the top level or the enclosing let expression (if there is one)
+- Replaced elm-analyse with our own diagnostics, there might be missing rules for you due to this
+- Show inferred type information on hovers
+- Support linked editing ranges. When editing a function name, the type annotation name will auto rename, or vice versa (needs to be enabled in most clients)
+- Watch elm.json for changes. The server will restart when a change is detected
+- Run elm make on server init
+- Improve record completions
+
+### Bug Fixes
+
+- Greatly improve performance of diagnostics and type inference
+- Fix some type inference cases
+- Fix module resolution to be more like how the compiler resolves modules
+- Fix some incorrect unused value diagnostics
+- Fix how errors from third party binaries are shown
+
+### Other Changes
+
+- Update package rankings
+- Update our parser
+
 ## 1.13.2
 
 - Fix bug on file open
diff --git a/README.md b/README.md
index 3d19480..d8fc97f 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ Supports Elm 0.19 and up
 | documentSymbols  | Identifies all symbols in a document.                                                                                                                |
 | folding          | Let's you fold the code on certain Elm constructs                                                                                                    |
 | hover            | Shows type annotations and documentation for a type alias, module, custom type or function                                                           |
+| linkedEditing    | Enables auto renaming a function name when the type annotation name is edited, or vice versa                                                         |
 | references       | Lists all references to a type alias, module, custom type or function                                                                                |
 | rename           | Enables you to rename a type alias, module, custom type or function                                                                                  |
 | workspaceSymbols | Identifies all symbols in the current workspace                                                                                                      |
@@ -136,20 +137,22 @@ Settings may need a restart to be applied.
 
 ## Editor Support
 
-| Editor                                                                                  |    Diagnostics     |     Formatting     |    Code Lenses     |    Completions     |    Definitions     |  Document Symbols  |      Folding       |       Hover        |     References     |       Rename       | Workspace Symbols  |
-| --------------------------------------------------------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: |
-| [VSCode](https://github.com/elm-tooling/elm-language-server#vscode)                     | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| [VIM CoC](https://github.com/elm-tooling/elm-language-server#cocnvim)                   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| [VIM LanguageClient](https://github.com/elm-tooling/elm-language-server#languageClient) | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: |  :grey_question:   |  :grey_question:   |  :grey_question:   |  :grey_question:   |  :grey_question:   |  :grey_question:   |  :grey_question:   |
-| [VIM ALE](https://github.com/elm-tooling/elm-language-server#ale)                       | :heavy_check_mark: |        :x:         |        :x:         |  :grey_question:   | :heavy_check_mark: |        :x:         |        :x:         | :heavy_check_mark: | :heavy_check_mark: |        :x:         | :heavy_check_mark: |
-| [Kakoune](https://github.com/elm-tooling/elm-language-server#kak-lsp)                   | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   |
-| [Emacs](https://github.com/elm-tooling/elm-language-server#emacs)                       | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
-| [Sublime](https://github.com/elm-tooling/elm-language-server#sublime)                   | :heavy_check_mark: | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
+| Editor                                                                                  |    Diagnostics     |     Formatting     |    Code Lenses     |    Completions     |    Definitions     |  Document Symbols  |      Folding       |       Hover        |   Linked Editing   |     References     |       Rename       | Workspace Symbols  |
+| --------------------------------------------------------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: |
+| [VSCode](https://github.com/elm-tooling/elm-language-server#vscode)                     | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
+| [VIM CoC](https://github.com/elm-tooling/elm-language-server#cocnvim)                   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
+| [VIM LanguageClient](https://github.com/elm-tooling/elm-language-server#languageClient) | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: |  :grey_question:   |  :grey_question:   |  :grey_question:   |  :grey_question:   |        :x:         |  :grey_question:   |  :grey_question:   |  :grey_question:   |
+| [VIM ALE](https://github.com/elm-tooling/elm-language-server#ale)                       | :heavy_check_mark: |        :x:         |        :x:         |  :grey_question:   | :heavy_check_mark: |        :x:         |        :x:         | :heavy_check_mark: |        :x:         | :heavy_check_mark: |        :x:         | :heavy_check_mark: |
+| [Kakoune](https://github.com/elm-tooling/elm-language-server#kak-lsp)                   | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   |
+| [Emacs](https://github.com/elm-tooling/elm-language-server#emacs)                       | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |  :grey_question:   | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
+| [Sublime](https://github.com/elm-tooling/elm-language-server#sublime)                   | :heavy_check_mark: | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |        :x:         | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
 
 ### VSCode
 
 Just install the [`elm-tooling/elm-language-client-vscode`](https://github.com/elm-tooling/elm-language-client-vscode) plugin from the [VSCode MarketPlace](https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode)
 
+To enable linked editing in VSCode, use the setting `"editor.linkedEditing": true`.
+
 ### Vim
 
 There are [general setup instructions and FAQ for Vim](https://github.com/elm-tooling/elm-vim).
diff --git a/package-lock.json b/package-lock.json
index 29fdbb2..0ef59da 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "@elm-tooling/elm-language-server",
-  "version": "2.0.0-RC2",
+  "version": "2.0.0-RC3",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -842,9 +842,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "14.14.14",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz",
-      "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==",
+      "version": "14.14.16",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz",
+      "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==",
       "dev": true
     },
     "@types/normalize-package-data": {
@@ -881,13 +881,13 @@
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz",
-      "integrity": "sha512-h6/V46o6aXpKRlarP1AiJEXuCJ7cMQdlpfMDrcllIgX3dFkLwEBTXAoNP98ZoOmqd1xvymMVRAI4e7yVvlzWEg==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.1.tgz",
+      "integrity": "sha512-fABclAX2QIEDmTMk6Yd7Muv1CzFLwWM4505nETzRHpP3br6jfahD9UUJkhnJ/g2m7lwfz8IlswcwGGPGiq9exw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "4.10.0",
-        "@typescript-eslint/scope-manager": "4.10.0",
+        "@typescript-eslint/experimental-utils": "4.11.1",
+        "@typescript-eslint/scope-manager": "4.11.1",
         "debug": "^4.1.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.0.0",
@@ -896,55 +896,55 @@
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.10.0.tgz",
-      "integrity": "sha512-opX+7ai1sdWBOIoBgpVJrH5e89ra1KoLrJTz0UtWAa4IekkKmqDosk5r6xqRaNJfCXEfteW4HXQAwMdx+jjEmw==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.1.tgz",
+      "integrity": "sha512-mAlWowT4A6h0TC9F+J5pdbEhjNiEMO+kqPKQ4sc3fVieKL71dEqfkKgtcFVSX3cjSBwYwhImaQ/mXQF0oaI38g==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/scope-manager": "4.10.0",
-        "@typescript-eslint/types": "4.10.0",
-        "@typescript-eslint/typescript-estree": "4.10.0",
+        "@typescript-eslint/scope-manager": "4.11.1",
+        "@typescript-eslint/types": "4.11.1",
+        "@typescript-eslint/typescript-estree": "4.11.1",
         "eslint-scope": "^5.0.0",
         "eslint-utils": "^2.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.10.0.tgz",
-      "integrity": "sha512-amBvUUGBMadzCW6c/qaZmfr3t9PyevcSWw7hY2FuevdZVp5QPw/K76VSQ5Sw3BxlgYCHZcK6DjIhSZK0PQNsQg==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.11.1.tgz",
+      "integrity": "sha512-BJ3jwPQu1jeynJ5BrjLuGfK/UJu6uwHxJ/di7sanqmUmxzmyIcd3vz58PMR7wpi8k3iWq2Q11KMYgZbUpRoIPw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "4.10.0",
-        "@typescript-eslint/types": "4.10.0",
-        "@typescript-eslint/typescript-estree": "4.10.0",
+        "@typescript-eslint/scope-manager": "4.11.1",
+        "@typescript-eslint/types": "4.11.1",
+        "@typescript-eslint/typescript-estree": "4.11.1",
         "debug": "^4.1.1"
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz",
-      "integrity": "sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.1.tgz",
+      "integrity": "sha512-Al2P394dx+kXCl61fhrrZ1FTI7qsRDIUiVSuN6rTwss6lUn8uVO2+nnF4AvO0ug8vMsy3ShkbxLu/uWZdTtJMQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.10.0",
-        "@typescript-eslint/visitor-keys": "4.10.0"
+        "@typescript-eslint/types": "4.11.1",
+        "@typescript-eslint/visitor-keys": "4.11.1"
       }
     },
     "@typescript-eslint/types": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.10.0.tgz",
-      "integrity": "sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.1.tgz",
+      "integrity": "sha512-5kvd38wZpqGY4yP/6W3qhYX6Hz0NwUbijVsX2rxczpY6OXaMxh0+5E5uLJKVFwaBM7PJe1wnMym85NfKYIh6CA==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz",
-      "integrity": "sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.1.tgz",
+      "integrity": "sha512-tC7MKZIMRTYxQhrVAFoJq/DlRwv1bnqA4/S2r3+HuHibqvbrPcyf858lNzU7bFmy4mLeIHFYr34ar/1KumwyRw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.10.0",
-        "@typescript-eslint/visitor-keys": "4.10.0",
+        "@typescript-eslint/types": "4.11.1",
+        "@typescript-eslint/visitor-keys": "4.11.1",
         "debug": "^4.1.1",
         "globby": "^11.0.1",
         "is-glob": "^4.0.1",
@@ -954,12 +954,12 @@
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "4.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz",
-      "integrity": "sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==",
+      "version": "4.11.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.1.tgz",
+      "integrity": "sha512-IrlBhD9bm4bdYcS8xpWarazkKXlE7iYb1HzRuyBP114mIaj5DJPo11Us1HgH60dTt41TCZXMaTCAW+OILIYPOg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.10.0",
+        "@typescript-eslint/types": "4.11.1",
         "eslint-visitor-keys": "^2.0.0"
       }
     },
@@ -2908,8 +2908,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
       "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "har-schema": {
       "version": "2.0.0",
@@ -3346,7 +3345,6 @@
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
       "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
       "dev": true,
-      "optional": true,
       "requires": {
         "is-docker": "^2.0.0"
       }
@@ -4309,11 +4307,10 @@
       "dev": true
     },
     "node-notifier": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz",
-      "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz",
+      "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==",
       "dev": true,
-      "optional": true,
       "requires": {
         "growly": "^1.3.0",
         "is-wsl": "^2.2.0",
@@ -5371,8 +5368,7 @@
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
       "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "signal-exit": {
       "version": "3.0.3",
@@ -6335,8 +6331,7 @@
       "version": "8.3.1",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
       "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "v8-compile-cache": {
       "version": "2.2.0",
diff --git a/package.json b/package.json
index e291d61..7923e30 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@elm-tooling/elm-language-server",
   "description": "Implementation of an elm language server in node.",
-  "version": "2.0.0-RC2",
+  "version": "2.0.0-RC3",
   "author": "Kolja Lampe",
   "license": "MIT",
   "main": "./out/module.js",
@@ -33,9 +33,9 @@
   },
   "devDependencies": {
     "@types/jest": "^26.0.19",
-    "@types/node": "14.14.14",
-    "@typescript-eslint/eslint-plugin": "^4.10.0",
-    "@typescript-eslint/parser": "^4.10.0",
+    "@types/node": "14.14.16",
+    "@typescript-eslint/eslint-plugin": "^4.11.0",
+    "@typescript-eslint/parser": "^4.11.0",
     "copyfiles": "^2.4.1",
     "doctoc": "^2.0.0",
     "eslint": "^7.16.0",
diff --git a/src/capabilityCalculator.ts b/src/capabilityCalculator.ts
index cb4d378..43f22b1 100644
--- a/src/capabilityCalculator.ts
+++ b/src/capabilityCalculator.ts
@@ -42,7 +42,7 @@ export class CapabilityCalculator {
       workspaceSymbolProvider: true,
       workspace: {
         fileOperations: {
-          willCreate: {
+          didCreate: {
             filters: [
               {
                 scheme: "file",
@@ -72,6 +72,7 @@ export class CapabilityCalculator {
           },
         },
       },
+      linkedEditingRangeProvider: true,
     };
   }
 }
diff --git a/src/elmWorkspace.ts b/src/elmWorkspace.ts
index ba7ccfc..c58294c 100644
--- a/src/elmWorkspace.ts
+++ b/src/elmWorkspace.ts
@@ -298,7 +298,7 @@ export class ElmWorkspace implements IElmWorkspace {
     let progress = 0;
     let elmVersion;
     try {
-      elmVersion = await utils.getElmVersion(
+      elmVersion = utils.getElmVersion(
         clientSettings,
         this.rootPath,
         this.connection,
@@ -337,17 +337,17 @@ export class ElmWorkspace implements IElmWorkspace {
         elmVersion,
       )}/`;
 
+      // Run `elm make` to download dependencies
       try {
-        // Run `elm make` to download dependencies
-        await utils.execCmd(
+        utils.execCmdSync(
           clientSettings.elmPath,
           "elm",
           { cmdArguments: ["make"] },
           this.rootPath.fsPath,
           this.connection,
         );
-      } catch {
-        // Compile failed
+      } catch (error) {
+        // On application projects, this will give a NO INPUT error message, but will still download the dependencies
       }
 
       this.elmPackageCache = new ElmPackageCache(
@@ -398,14 +398,12 @@ export class ElmWorkspace implements IElmWorkspace {
 
     if (elmJson.type === "application") {
       const allDependencies = new Map(
-        Object.entries(
-          Object.assign(
-            elmJson.dependencies.direct,
-            elmJson.dependencies.indirect,
-            elmJson["test-dependencies"].direct,
-            elmJson["test-dependencies"].indirect,
-          ),
-        ).map(([dep, version]) => [dep, utils.parseVersion(version)]),
+        Object.entries({
+          ...elmJson.dependencies.direct,
+          ...elmJson.dependencies.indirect,
+          ...elmJson["test-dependencies"].direct,
+          ...elmJson["test-dependencies"].indirect,
+        }).map(([dep, version]) => [dep, utils.parseVersion(version)]),
       );
 
       return {
diff --git a/src/providers/diagnostics/elmDiagnosticsHelper.ts b/src/providers/diagnostics/elmDiagnosticsHelper.ts
index caaac55..e440bc7 100644
--- a/src/providers/diagnostics/elmDiagnosticsHelper.ts
+++ b/src/providers/diagnostics/elmDiagnosticsHelper.ts
@@ -1,4 +1,3 @@
-import path from "path";
 import { DiagnosticSeverity, Range } from "vscode-languageserver";
 import { URI, Utils } from "vscode-uri";
 import { Diagnostics } from "../../util/types/diagnostics";
diff --git a/src/providers/diagnostics/elmLsDiagnostics.ts b/src/providers/diagnostics/elmLsDiagnostics.ts
index 067af4e..c57ce4e 100644
--- a/src/providers/diagnostics/elmLsDiagnostics.ts
+++ b/src/providers/diagnostics/elmLsDiagnostics.ts
@@ -25,16 +25,24 @@ import * as path from "path";
 interface IElmAnalyseJson {
   checks?: {
     BooleanCase?: boolean;
+    DebugLog?: boolean; // We don't support this
+    DebugTodo?: boolean; // We don't support this
     DropConcatOfLists?: boolean;
     DropConsOfItemAndList?: boolean;
-    ExposeAll?: boolean;
+    DuplicateImport?: boolean; // We don't support this as elm-format will fix this
+    DuplicateImportedVariable?: boolean; // We don't support this as elm-format will fix this
+    ExposeAll?: boolean; // We don't support this
+    FileLoadFailed?: boolean; // We don't support this as it makes no sense for us
     NoUncurriedPrefix?: boolean;
-    ImportAll?: boolean;
+    FunctionInLet?: boolean; // We don't support this
+    ImportAll?: boolean; // We don't support this
     MapNothingToNothing?: boolean;
-    NoTopLevelSignature?: boolean;
+    MultiLineRecordFormatting?: boolean; // We don't support this
+    NoTopLevelSignature?: boolean; // We don't support this as we get it via type inference already
     SingleFieldRecord?: boolean;
+    TriggerWords?: string[]; // We don't support this
     UnnecessaryListConcat?: boolean;
-    UnnecessaryParens?: boolean;
+    UnnecessaryParens?: boolean; // We don't support this as elm-format will fix these anyway
     UnnecessaryPortModule?: boolean;
     UnusedImport?: boolean;
     UnusedImportAlias?: boolean;
@@ -362,10 +370,19 @@ export class ElmLsDiagnostics {
     elmWorkspace: IElmWorkspace,
   ): IDiagnostic[] => {
     const elmAnalyseJson = this.getElmAnalyseJson(
-      elmWorkspace.getRootPath().toString(),
+      elmWorkspace.getRootPath().fsPath,
     );
     const tree = treeContainer.tree;
     const uri = treeContainer.uri;
+
+    if (
+      elmAnalyseJson.excludedPaths?.some((path) =>
+        uri.startsWith(URI.file(path).toString()),
+      )
+    ) {
+      return [];
+    }
+
     try {
       return [
         ...(elmAnalyseJson.checks?.UnusedImport === false
@@ -956,7 +973,7 @@ export class ElmLsDiagnostics {
     ) {
       diagnostics.push({
         range: this.getNodeRange(unusedPortMatches[0].captures[0].node),
-        message: `Module is definined as a \`port\` module, but does not define any ports.`,
+        message: `Module is defined as a \`port\` module, but does not define any ports.`,
         severity: DiagnosticSeverity.Warning,
         source: "ElmLS",
         data: { uri: tree.uri, code: "unnecessary_port_module" },
diff --git a/src/providers/diagnostics/elmMakeDiagnostics.ts b/src/providers/diagnostics/elmMakeDiagnostics.ts
index 9975cd2..0d821b6 100644
--- a/src/providers/diagnostics/elmMakeDiagnostics.ts
+++ b/src/providers/diagnostics/elmMakeDiagnostics.ts
@@ -14,7 +14,6 @@ import {
 import { URI } from "vscode-uri";
 import { ITreeContainer } from "../../forest";
 import * as utils from "../../util/elmUtils";
-import { execCmd } from "../../util/elmUtils";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { Settings } from "../../util/settings";
 import { TreeUtils } from "../../util/treeUtils";
@@ -135,6 +134,10 @@ export class ElmMakeDiagnostics {
     return this.convertDiagnosticsToCodeActions(elmMakeDiagnostics, uri);
   }
 
+  private hasType(error: any): error is IElmError | IElmCompilerError {
+    return "type" in error;
+  }
+
   private convertDiagnosticsToCodeActions(
     diagnostics: IDiagnostic[],
     uri: string,
@@ -407,125 +410,148 @@ export class ElmMakeDiagnostics {
       `Find entrypoints: ${message}. See https://github.com/elm-tooling/elm-language-server#configuration for more information.`,
     );
 
-    return new Promise(async (resolve) => {
-      const argsMake = [
-        "make",
-        ...relativePathsToFiles,
-        "--report",
-        "json",
-        "--output",
-        "/dev/null",
-      ];
-
-      const argsTest = [
-        "make",
-        ...relativePathsToFiles,
-        "--report",
-        "json",
-        "--output",
-        "/dev/null",
-      ];
-
-      const makeCommand: string = settings.elmPath;
-      const testCommand: string = settings.elmTestPath;
-      const isTestFile = utils.isTestFile(filePath, workspaceRootPath);
-      const args = isTestFile ? argsTest : argsMake;
-      const testOrMakeCommand = isTestFile ? testCommand : makeCommand;
-      const testOrMakeCommandWithOmittedSettings = isTestFile
-        ? "elm-test"
-        : "elm";
-      const options = {
-        cmdArguments: args,
-        notFoundText: isTestFile
-          ? "'elm-test' is not available. Install Elm via 'npm install -g elm-test'."
-          : "The 'elm' compiler is not available. Install Elm via 'npm install -g elm'.",
-      };
-
-      try {
-        // Do nothing on success, but return that there were no errors
-        await execCmd(
-          testOrMakeCommand,
-          testOrMakeCommandWithOmittedSettings,
-          options,
-          workspaceRootPath,
-          this.connection,
-        );
-        resolve([]);
-      } catch (error) {
-        if (typeof error === "string") {
-          resolve([]);
-        } else {
-          const execaError = error as execa.ExecaReturnValue<string>;
-          const lines: IElmIssue[] = [];
-          execaError.stderr.split("\n").forEach((line: string) => {
-            let errorObject: any;
-            try {
-              errorObject = JSON.parse(line);
-            } catch (error) {
-              this.connection.console.warn(
-                "Received an invalid json, skipping error.",
+    const argsMake = [
+      "make",
+      ...relativePathsToFiles,
+      "--report",
+      "json",
+      "--output",
+      "/dev/null",
+    ];
+
+    const argsTest = [
+      "make",
+      ...relativePathsToFiles,
+      "--report",
+      "json",
+      "--output",
+      "/dev/null",
+    ];
+
+    const makeCommand: string = settings.elmPath;
+    const testCommand: string = settings.elmTestPath;
+    const isTestFile = utils.isTestFile(filePath, workspaceRootPath);
+    const args = isTestFile ? argsTest : argsMake;
+    const testOrMakeCommand = isTestFile ? testCommand : makeCommand;
+    const testOrMakeCommandWithOmittedSettings = isTestFile
+      ? "elm-test"
+      : "elm";
+    const options = {
+      cmdArguments: args,
+      notFoundText: isTestFile
+        ? "'elm-test' is not available. Install Elm via 'npm install -g elm-test'."
+        : "The 'elm' compiler is not available. Install Elm via 'npm install -g elm'.",
+    };
+
+    try {
+      // Do nothing on success, but return that there were no errors
+      utils.execCmdSync(
+        testOrMakeCommand,
+        testOrMakeCommandWithOmittedSettings,
+        options,
+        workspaceRootPath,
+        this.connection,
+      );
+      return [];
+    } catch (error) {
+      if (typeof error === "string") {
+        return [];
+      } else {
+        const execaError = error as execa.ExecaReturnValue<string>;
+        const lines: IElmIssue[] = [];
+        execaError.stderr.split("\n").forEach((line: string) => {
+          let errorObject: unknown;
+          try {
+            errorObject = JSON.parse(line);
+          } catch (error) {
+            this.connection.console.warn(
+              "Received an invalid json, skipping error.",
+            );
+          }
+
+          if (
+            errorObject &&
+            this.hasType(errorObject) &&
+            errorObject.type === "compile-errors"
+          ) {
+            const compilerError = errorObject as IElmCompilerError;
+            compilerError.errors.forEach((error: IError) => {
+              const problems: IElmIssue[] = error.problems.map(
+                (problem: IProblem) => ({
+                  details: problem.message
+                    .map((message: string | IStyledString) =>
+                      typeof message === "string"
+                        ? message
+                        : `#${message.string}#`,
+                    )
+                    .join(""),
+                  file: error.path
+                    ? path.isAbsolute(error.path)
+                      ? path.relative(workspaceRootPath, error.path)
+                      : error.path
+                    : relativePathsToFiles[0],
+                  overview: problem.title,
+                  region: problem.region,
+                  subregion: "",
+                  tag: "error",
+                  type: "error",
+                }),
               );
-            }
 
-            if (errorObject && errorObject.type === "compile-errors") {
-              errorObject.errors.forEach((error: IError) => {
-                const problems: IElmIssue[] = error.problems.map(
-                  (problem: IProblem) => ({
-                    details: problem.message
-                      .map((message: string | IStyledString) =>
-                        typeof message === "string"
-                          ? message
-                          : `#${message.string}#`,
-                      )
-                      .join(""),
-                    file: error.path
-                      ? path.isAbsolute(error.path)
-                        ? path.relative(workspaceRootPath, error.path)
-                        : error.path
-                      : relativePathsToFiles[0],
-                    overview: problem.title,
-                    region: problem.region,
-                    subregion: "",
-                    tag: "error",
-                    type: "error",
-                  }),
-                );
-
-                lines.push(...problems);
-              });
-            } else if (errorObject && errorObject.type === "error") {
-              const problem: IElmIssue = {
-                details: errorObject.message
-                  .map((message: string | IStyledString) =>
-                    typeof message === "string" ? message : message.string,
-                  )
-                  .join(""),
-                // elm-test might supply absolute paths to files
-                file: errorObject.path
-                  ? path.relative(workspaceRootPath, errorObject.path)
-                  : relativePathsToFiles[0],
-                overview: errorObject.title,
-                region: {
-                  end: {
-                    column: 1,
-                    line: 1,
-                  },
-                  start: {
-                    column: 1,
-                    line: 1,
-                  },
+              lines.push(...problems);
+            });
+          } else if (
+            errorObject &&
+            this.hasType(errorObject) &&
+            errorObject.type === "error"
+          ) {
+            const error = errorObject as IElmError;
+            this.checkIfVersionMismatchesAndCreateMessage(error);
+
+            const problem: IElmIssue = {
+              details: error.message
+                .map((message: string | IStyledString) =>
+                  typeof message === "string" ? message : message.string,
+                )
+                .join(""),
+              // elm-test might supply absolute paths to files
+              file: error.path
+                ? path.relative(workspaceRootPath, error.path)
+                : relativePathsToFiles[0],
+              overview: error.title,
+              region: {
+                end: {
+                  column: 1,
+                  line: 1,
+                },
+                start: {
+                  column: 1,
+                  line: 1,
                 },
-                subregion: "",
-                tag: "error",
-                type: "error",
-              };
+              },
+              subregion: "",
+              tag: "error",
+              type: "error",
+            };
 
-              lines.push(problem);
-            }
-          });
-          resolve(lines);
-        }
+            lines.push(problem);
+          }
+        });
+        return lines;
       }
-    });
+    }
+  }
+  private checkIfVersionMismatchesAndCreateMessage(
+    errorObject: IElmError,
+  ): void {
+    if (errorObject.title === "ELM VERSION MISMATCH") {
+      this.connection.window.showErrorMessage(
+        errorObject.message
+          .map((message: string | IStyledString) =>
+            typeof message === "string" ? message : message.string,
+          )
+          .join(""),
+      );
+    }
   }
 }
diff --git a/src/providers/documentFormatingProvider.ts b/src/providers/documentFormatingProvider.ts
index 2cf748a..1aaec89 100644
--- a/src/providers/documentFormatingProvider.ts
+++ b/src/providers/documentFormatingProvider.ts
@@ -7,7 +7,7 @@ import {
 import { URI } from "vscode-uri";
 import { DiagnosticsProvider } from ".";
 import * as Diff from "../util/diff";
-import { execCmd } from "../util/elmUtils";
+import { execCmdSync } from "../util/elmUtils";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { Settings } from "../util/settings";
 import { TextDocumentEvents } from "../util/textDocumentEvents";
@@ -36,7 +36,7 @@ export class DocumentFormattingProvider {
     );
   }
 
-  public formatText = async (
+  private formatText = (
     elmWorkspaceRootPath: URI,
     elmFormatPath: string,
     text: string,
@@ -46,19 +46,15 @@ export class DocumentFormattingProvider {
       notFoundText: "Install elm-format via 'npm install -g elm-format",
     };
 
-    try {
-      const format = await execCmd(
-        elmFormatPath,
-        "elm-format",
-        options,
-        elmWorkspaceRootPath.fsPath,
-        this.connection,
-        text,
-      );
-      return Diff.getTextRangeChanges(text, format.stdout);
-    } catch (error) {
-      this.connection.console.warn(JSON.stringify(error));
-    }
+    const format = execCmdSync(
+      elmFormatPath,
+      "elm-format",
+      options,
+      elmWorkspaceRootPath.fsPath,
+      this.connection,
+      text,
+    );
+    return Diff.getTextRangeChanges(text, format.stdout);
   };
 
   protected handleFormattingRequest = async (
@@ -73,7 +69,7 @@ export class DocumentFormattingProvider {
       }
 
       const settings = await this.settings.getClientSettings();
-      return await this.formatText(
+      return this.formatText(
         params.program.getRootPath(),
         settings.elmFormatPath,
         text.getText(),
diff --git a/src/providers/handlers/fileEventsHandler.ts b/src/providers/handlers/fileEventsHandler.ts
index e2d2430..12e3175 100644
--- a/src/providers/handlers/fileEventsHandler.ts
+++ b/src/providers/handlers/fileEventsHandler.ts
@@ -34,12 +34,12 @@ export class FileEventsHandler {
     this.connection = container.resolve<Connection>("Connection");
     this.astProvider = container.resolve(ASTProvider);
 
-    this.connection.workspace.onWillCreateFiles((params: CreateFilesParams) => {
+    this.connection.workspace.onDidCreateFiles((params: CreateFilesParams) => {
       const edit: WorkspaceEdit = { changes: {} };
       for (const { uri } of params.files) {
         const changes = new ElmWorkspaceMatcher(({ uri }: FileCreate) =>
           URI.parse(uri),
-        ).handle(this.onWillCreateFile.bind(this))({
+        ).handle(this.onDidCreateFile.bind(this))({
           uri,
         });
 
@@ -47,7 +47,8 @@ export class FileEventsHandler {
           edit.changes[uri] = changes;
         }
       }
-      return edit;
+
+      void this.connection.workspace.applyEdit(edit);
     });
 
     this.connection.workspace.onWillRenameFiles((params: RenameFilesParams) => {
@@ -80,7 +81,7 @@ export class FileEventsHandler {
     });
   }
 
-  private onWillCreateFile({
+  private onDidCreateFile({
     uri,
     program,
   }: ICreateFileParams): TextEdit[] | undefined {
diff --git a/src/providers/index.ts b/src/providers/index.ts
index e39c937..0d09846 100644
--- a/src/providers/index.ts
+++ b/src/providers/index.ts
@@ -13,4 +13,5 @@ export * from "./hoverProvider";
 export * from "./referencesProvider";
 export * from "./renameProvider";
 export * from "./workspaceSymbolProvider";
+export * from "./linkedEditingRangesProvider";
 import "./codeAction";
diff --git a/src/providers/linkedEditingRangesProvider.ts b/src/providers/linkedEditingRangesProvider.ts
new file mode 100644
index 0000000..69d16dd
--- /dev/null
+++ b/src/providers/linkedEditingRangesProvider.ts
@@ -0,0 +1,77 @@
+import { container } from "tsyringe";
+import {
+  Connection,
+  LinkedEditingRangeParams,
+  LinkedEditingRangeRequest,
+  LinkedEditingRanges,
+  Range,
+} from "vscode-languageserver";
+import { URI } from "vscode-uri";
+import { PositionUtil } from "../positionUtil";
+import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
+import { TreeUtils } from "../util/treeUtils";
+import { ILinkedEditingRangeParams } from "./paramsExtensions";
+
+export class LinkedEditiRangesProvider {
+  private connection: Connection;
+
+  constructor() {
+    this.connection = container.resolve<Connection>("Connection");
+    this.connection.onRequest(
+      LinkedEditingRangeRequest.type,
+      new ElmWorkspaceMatcher((params: LinkedEditingRangeParams) =>
+        URI.parse(params.textDocument.uri),
+      ).handle(this.provideLinkedEditingRanges.bind(this)),
+    );
+  }
+
+  protected provideLinkedEditingRanges = (
+    params: ILinkedEditingRangeParams,
+  ): LinkedEditingRanges => {
+    const ranges: Range[] = [];
+    const nodeAtPostion = TreeUtils.getNamedDescendantForPosition(
+      params.sourceFile.tree.rootNode,
+      params.position,
+    );
+
+    const range: Range = {
+      start: PositionUtil.FROM_TS_POSITION(
+        nodeAtPostion.startPosition,
+      ).toVSPosition(),
+      end: PositionUtil.FROM_TS_POSITION(
+        nodeAtPostion.endPosition,
+      ).toVSPosition(),
+    };
+
+    if (
+      nodeAtPostion.parent?.type === "function_declaration_left" &&
+      TreeUtils.getTypeAnnotation(nodeAtPostion.parent.parent ?? undefined)
+    ) {
+      ranges.push(this.addLinesToRange(range, -1));
+      ranges.push(range);
+    }
+
+    if (
+      nodeAtPostion.parent?.type === "type_annotation" &&
+      nodeAtPostion.type === "lower_case_identifier"
+    ) {
+      ranges.push(range);
+      ranges.push(this.addLinesToRange(range, 1));
+    }
+
+    return { ranges };
+  };
+
+  private addLinesToRange(range: Range, linesToAdd: number): Range {
+    return {
+      start: {
+        line: range.start.line + linesToAdd,
+        character: range.start.character,
+      },
+      end: {
+        line: range.end.line + linesToAdd,
+        character: range.end.character,
+      },
+    };
+  }
+}
diff --git a/src/providers/paramsExtensions.ts b/src/providers/paramsExtensions.ts
index 0431592..13d54ff 100644
--- a/src/providers/paramsExtensions.ts
+++ b/src/providers/paramsExtensions.ts
@@ -10,6 +10,7 @@ import {
   FileDelete,
   FileRename,
   FoldingRangeParams,
+  LinkedEditingRangeParams,
   PrepareRenameParams,
   ReferenceParams,
   RenameParams,
@@ -35,3 +36,4 @@ export type IDidOpenTextDocumentParams = DidOpenTextDocumentParams & IParams;
 export type ICreateFileParams = FileCreate & IParams;
 export type IRenameFileParams = FileRename & IParams;
 export type IDeleteFileParams = FileDelete & IParams;
+export type ILinkedEditingRangeParams = LinkedEditingRangeParams & IParams;
diff --git a/src/server.ts b/src/server.ts
index 5914734..4f158b7 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -22,6 +22,7 @@ import {
   ElmMakeDiagnostics,
   FoldingRangeProvider,
   HoverProvider,
+  LinkedEditiRangesProvider,
   ReferencesProvider,
   RenameProvider,
   SelectionRangeProvider,
@@ -165,6 +166,7 @@ export class Server implements ILanguageServer {
     new SelectionRangeProvider();
     new RenameProvider();
     new FileEventsHandler();
+    new LinkedEditiRangesProvider();
   }
 
   private getElmJsonFolder(uri: string): URI {
diff --git a/src/util/elmUtils.ts b/src/util/elmUtils.ts
index b220587..98a2269 100644
--- a/src/util/elmUtils.ts
+++ b/src/util/elmUtils.ts
@@ -1,4 +1,4 @@
-import execa, { ExecaReturnValue } from "execa";
+import execa, { ExecaSyncReturnValue } from "execa";
 import * as path from "./path";
 import { Connection, CompletionItemKind } from "vscode-languageserver";
 import { URI } from "vscode-uri";
@@ -7,7 +7,7 @@ import { IClientSettings } from "./settings";
 
 export const isWindows = process.platform === "win32";
 
-/** Options for execCmd */
+/** Options for execCmdSync */
 export interface IExecCmdOptions {
   /** Any arguments */
   cmdArguments?: string[];
@@ -19,21 +19,21 @@ export interface IExecCmdOptions {
 }
 
 /** Executes a command. Shows an error message if the command isn't found */
-export async function execCmd(
+export function execCmdSync(
   cmdFromUser: string,
   cmdStatic: string,
   options: IExecCmdOptions = {},
   cwd: string,
   connection: Connection,
   input?: string,
-): Promise<ExecaReturnValue<string>> {
+): ExecaSyncReturnValue<string> {
   const cmd = cmdFromUser === "" ? cmdStatic : cmdFromUser;
   const preferLocal = cmdFromUser === "";
 
   const cmdArguments = options ? options.cmdArguments : [];
 
   try {
-    return await execa(cmd, cmdArguments, {
+    return execa.sync(cmd, cmdArguments, {
       cwd,
       input,
       preferLocal,
@@ -46,9 +46,11 @@ export async function execCmd(
           ? options.notFoundText
           : `Cannot find executable with name '${cmd}'`,
       );
-      return Promise.reject("Executable not found");
+      connection.console.warn("Executable not found");
+      throw "Executable not found";
     } else {
-      return Promise.reject(error);
+      connection.console.warn(JSON.stringify(error));
+      throw error;
     }
   }
 }
@@ -84,18 +86,18 @@ export function getEmptyTypes(): {
   ];
 }
 
-export async function getElmVersion(
+export function getElmVersion(
   settings: IClientSettings,
   elmWorkspaceFolder: URI,
   connection: Connection,
-): Promise<string> {
+): string {
   const options = {
     cmdArguments: ["--version"],
     notFoundText:
       "Elm binary not found, did you install and setup the path to your binary?",
   };
 
-  const result = await execCmd(
+  const result = execCmdSync(
     settings.elmPath,
     "elm",
     options,
@@ -107,7 +109,7 @@ export async function getElmVersion(
 
   connection.console.info(`Elm version ${version} detected.`);
 
-  return Promise.resolve(version);
+  return version;
 }
 
 type SolverResult =
diff --git a/src/util/types/recordFieldReferenceTable.ts b/src/util/types/recordFieldReferenceTable.ts
index cf72f66..e5637b4 100644
--- a/src/util/types/recordFieldReferenceTable.ts
+++ b/src/util/types/recordFieldReferenceTable.ts
@@ -1,35 +1,36 @@
 import { SyntaxNode } from "web-tree-sitter";
 import { EFieldType } from "./expressionTree";
+import { SyntaxNodeSet } from "./syntaxNodeSet";
 
 export class RecordFieldReferenceTable {
-  private refsByField: Map<string, SyntaxNode[]>;
+  private refsByField: Map<string, SyntaxNodeSet>;
   private _frozen = false;
 
   public get frozen(): boolean {
     return this._frozen;
   }
 
-  constructor(refsByField?: Map<string, SyntaxNode[]>) {
-    this.refsByField = refsByField ?? new Map<string, SyntaxNode[]>();
+  constructor(refsByField?: Map<string, SyntaxNodeSet>) {
+    this.refsByField = refsByField ?? new Map<string, SyntaxNodeSet>();
   }
 
   public static fromExpressions(
     fieldExpressions: EFieldType[],
   ): RecordFieldReferenceTable {
-    const fieldRefs: Map<string, SyntaxNode[]> = new Map<
+    const fieldRefs: Map<string, SyntaxNodeSet> = new Map<
       string,
-      SyntaxNode[]
+      SyntaxNodeSet
     >();
 
     fieldExpressions.forEach((field) => {
-      fieldRefs.set(field.name, [field]);
+      fieldRefs.set(field.name, new SyntaxNodeSet(field));
     });
 
     return new RecordFieldReferenceTable(fieldRefs);
   }
 
   public get(field: string): SyntaxNode[] {
-    return this.refsByField.get(field) ?? [];
+    return this.refsByField.get(field)?.toArray() ?? [];
   }
 
   public addAll(other: RecordFieldReferenceTable): void {
@@ -41,20 +42,26 @@ export class RecordFieldReferenceTable {
       let set = this.refsByField.get(field);
 
       if (!set) {
-        this.refsByField.set(field, []);
-        set = this.refsByField.get(field) ?? [];
+        set = new SyntaxNodeSet();
+        this.refsByField.set(field, set);
       }
 
-      set.push(...refs);
+      set.addAll(refs);
     });
   }
 
   public plus(other: RecordFieldReferenceTable): RecordFieldReferenceTable {
-    const newRefs = new Map<string, SyntaxNode[]>();
+    const newRefs = new Map<string, SyntaxNodeSet>();
 
     this.refsByField.forEach((set, field) => {
-      const otherSet = other.refsByField.get(field) ?? [];
-      newRefs.set(field, [...set, ...otherSet]);
+      const newRefSet = new SyntaxNodeSet().addAll(set);
+      const otherSet = other.refsByField.get(field);
+
+      if (otherSet) {
+        newRefSet.addAll(otherSet);
+      }
+
+      newRefs.set(field, newRefSet);
     });
     other.refsByField.forEach((set, field) => {
       if (!newRefs.get(field)) {
diff --git a/src/util/types/syntaxNodeSet.ts b/src/util/types/syntaxNodeSet.ts
new file mode 100644
index 0000000..e83f65c
--- /dev/null
+++ b/src/util/types/syntaxNodeSet.ts
@@ -0,0 +1,50 @@
+import { SyntaxNode } from "web-tree-sitter";
+
+export class SyntaxNodeSet<K extends SyntaxNode = SyntaxNode> {
+  private map = new Map<number, K>();
+
+  constructor(...items: K[]) {
+    items.forEach((value) => {
+      this.add(value);
+    });
+  }
+  public get size(): number {
+    return this.map.size;
+  }
+
+  public add(value: K): this {
+    this.map.set(value.id, value);
+    return this;
+  }
+
+  public clear(): void {
+    this.map.clear();
+  }
+
+  public delete(value: K): boolean {
+    return this.map.delete(value.id);
+  }
+
+  public forEach(callbackfn: (value: K) => void): void {
+    this.map.forEach((value) => {
+      callbackfn(value);
+    });
+  }
+
+  public has(value: K): boolean {
+    return this.map.has(value.id);
+  }
+
+  public values(): IterableIterator<K> {
+    return this.map.values();
+  }
+
+  public toArray(): K[] {
+    return Array.from(this.values());
+  }
+
+  public addAll(other: SyntaxNodeSet<K>): this {
+    other.forEach((value) => this.add(value));
+    return this;
+  }
+}
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index f9fd06c..f18630f 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -852,6 +852,7 @@ export class InferenceScope {
     const result = this.inferChild(
       (inference) => inference.inferDeclaration(declaration, false),
       new Set(activeScopes.values()),
+      /* recursionAllowed */ declaration.params.length > 0,
     );
 
     this.resolvedDeclarations.set(declaration, result.type);


Test patch:
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index 889fa7b..57c8664 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -18,11 +18,12 @@ import { baseUri, SourceTreeParser } from "../utils/sourceTreeParser";
 
 const basicsSources = `
 --@ Basics.elm
-module Basics exposing ((+), (|>), (==), Int, Float, Bool(..), Order(..))
+module Basics exposing ((+), (|>), (>>), (==), Int, Float, Bool(..), Order(..), negate)
 
 infix left  0 (|>) = apR
 infix non   4 (==) = eq
 infix left  6 (+)  = add
+infix right 9 (>>) = composeR
 
 type Int = Int
 
@@ -42,7 +43,15 @@ eq : a -> a -> Bool
 eq =
   Elm.Kernel.Utils.equal
 
+composeR : (a -> b) -> (b -> c) -> (a -> c)
+composeR f g x =
+  g (f x)
+
 type Order = LT | EQ | GT
+
+negate : number -> number
+negate n =
+  -n
 `;
 describe("test elm diagnostics", () => {
   const treeParser = new SourceTreeParser();
@@ -55,6 +64,7 @@ describe("test elm diagnostics", () => {
       message: IDiagnosticMessage;
       args: (string | number)[];
     }[],
+    disableSuggestionDiagnostics?: boolean,
   ) {
     await treeParser.init();
 
@@ -79,7 +89,10 @@ describe("test elm diagnostics", () => {
       if (!treeContainer.uri.includes("Basic")) {
         diagnostics.push(...program.getSyntacticDiagnostics(treeContainer));
         diagnostics.push(...program.getSemanticDiagnostics(treeContainer));
-        diagnostics.push(...program.getSuggestionDiagnostics(treeContainer));
+
+        if (!disableSuggestionDiagnostics) {
+          diagnostics.push(...program.getSuggestionDiagnostics(treeContainer));
+        }
       }
     });
 
@@ -219,4 +232,113 @@ field =
   `;
     await testTypeInference(basicsSources + source, []);
   });
+
+  test("field references accessed in complex ways", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+type alias TopChart a comparable =
+    { toValue : a -> Int
+    , items : List a
+    , toValueLabel : a -> Int
+    , toLabel : a -> Int
+    , sorter : a -> comparable
+    , filter : a -> Bool
+    }
+
+
+topChart : List { name : Int, amount : Int } -> TopChart { name : Int, amount : Int } Int
+topChart items =
+    default
+        { toValue = .amount
+        , items = items
+        }
+        |> withSorter (.amount >> negate)
+        |> withFilter (.amount >> (\\x -> x > 0))
+
+
+default : { toValue : a -> Int, items : List a } -> TopChart a Int
+default { toValue, items } =
+    { toValue = toValue
+    , items = items
+    , toValueLabel = \\_ -> 1
+    , toLabel = toValue
+    , sorter = toValue
+    , filter = \\_ -> True
+    }
+
+
+withSorter : (a -> comparable2) -> TopChart a comparable -> TopChart a comparable2
+withSorter sorter chart =
+    { toValue = chart.toValue
+    , items = chart.items
+    , toValueLabel = chart.toValueLabel
+    , toLabel = chart.toLabel
+    , sorter = sorter
+    , filter = chart.filter
+    }
+
+
+withFilter : (a -> Bool) -> TopChart a comparable -> TopChart a comparable
+withFilter filter chart =
+    { chart | filter = filter }
+    `;
+
+    await testTypeInference(basicsSources + source, []);
+  });
+
+  test("recursion inside a let function with params", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func : Int -> Int
+func =
+    let
+        go n =
+            if n == 1 then
+                2
+
+            else
+                func n + 1
+    in
+    go
+
+
+func2 =
+    let
+        go n =
+            if n == 1 then
+                "text"
+
+            else
+                func2 n 
+    in
+    go
+  `;
+    await testTypeInference(basicsSources + source, [], true);
+
+    const source2 = `
+    --@ Test.elm
+module Test exposing (..)
+
+func =
+     --^
+    let
+        go =
+            if False then
+                "text"
+
+            else
+                func
+    in
+    go
+      `;
+    await testTypeInference(
+      basicsSources + source2,
+      [{ message: Diagnostics.InfiniteRecursion, args: [] }],
+      true,
+    );
+  });
 });
diff --git a/test/diagnosticTests/elmLsDiagnostics.test.ts b/test/diagnosticTests/elmLsDiagnostics.test.ts
index 955c7ae..fd1ed0c 100644
--- a/test/diagnosticTests/elmLsDiagnostics.test.ts
+++ b/test/diagnosticTests/elmLsDiagnostics.test.ts
@@ -1277,7 +1277,7 @@ foo =
   describe("unnecessary port module", () => {
     const diagnosticWithRange = (range: Range): IDiagnostic => {
       return {
-        message: `Module is definined as a \`port\` module, but does not define any ports.`,
+        message: `Module is defined as a \`port\` module, but does not define any ports.`,
         source: "ElmLS",
         severity: DiagnosticSeverity.Warning,
         range,
diff --git a/test/fileEventsHandler.test.ts b/test/fileEventsHandler.test.ts
index a8a0f64..6a9466a 100644
--- a/test/fileEventsHandler.test.ts
+++ b/test/fileEventsHandler.test.ts
@@ -7,6 +7,7 @@ import {
   CreateFilesParams,
   DeleteFilesParams,
   HandlerResult,
+  NotificationHandler,
   Position,
   Range,
   RenameFilesParams,
@@ -23,11 +24,7 @@ import { baseUri, SourceTreeParser } from "./utils/sourceTreeParser";
 describe("fileEventsHandler", () => {
   const treeParser = new SourceTreeParser();
 
-  let createFilesHandler: RequestHandler<
-    CreateFilesParams,
-    WorkspaceEdit | null,
-    never
-  >;
+  let createFilesHandler: NotificationHandler<CreateFilesParams>;
   let renameFilesHandler: RequestHandler<
     RenameFilesParams,
     WorkspaceEdit | null,
@@ -38,13 +35,21 @@ describe("fileEventsHandler", () => {
     WorkspaceEdit | null,
     never
   >;
+  let appliedWorkspaceEdit: WorkspaceEdit;
 
   container.register("Connection", {
     useValue: mockDeep<Connection>({
       workspace: {
-        onWillCreateFiles: (handler) => (createFilesHandler = handler),
+        onDidCreateFiles: (handler) => (createFilesHandler = handler),
         onWillRenameFiles: (handler) => (renameFilesHandler = handler),
         onWillDeleteFiles: (handler) => (deleteFilesHandler = handler),
+        applyEdit: (edit) => {
+          if (WorkspaceEdit.is(edit)) {
+            appliedWorkspaceEdit = edit;
+          }
+
+          return Promise.resolve({ applied: true });
+        },
       },
     }),
   });
@@ -95,9 +100,9 @@ describe("fileEventsHandler", () => {
   it("handles file create event", async () => {
     await createProgram("");
     const newPath = uri("New/Module.elm");
-    const result = createFilesHandler({ files: [{ uri: newPath }] }, token);
+    createFilesHandler({ files: [{ uri: newPath }] });
 
-    const edit = await getEditFromResult(result);
+    const edit = appliedWorkspaceEdit;
 
     if (!edit.changes) {
       fail();
@@ -122,12 +127,9 @@ describe("fileEventsHandler", () => {
     await createProgram("");
     const newPath = uri("New/Module.elm");
     const newPath2 = uri("New/Another/Module.elm");
-    const result = createFilesHandler(
-      { files: [{ uri: newPath }, { uri: newPath2 }] },
-      token,
-    );
+    createFilesHandler({ files: [{ uri: newPath }, { uri: newPath2 }] });
 
-    const edit = await getEditFromResult(result);
+    const edit = appliedWorkspaceEdit;
 
     if (!edit.changes) {
       fail();