Repository: rokucommunity/brighterscript
Pull request #1241 (2024-06-28 13:51:32)

Refine Diagnostic Filters and adds human-readable diagnostic names
Addresses #1060 

- Changes the structure of `bsconfig.json` `"diagnosticFilters"` option to look like this:

```
Array<string | number | { files?: string | Array<string | { src: string } | { dest: string }>; codes?: Array<number | string> }>;
```

In more detail:
```jsonc
{
"diagnosticFilters": [
    123,   // numbers are applied to diagnostic code or legacyCode against all files
    "some-diagnostic-name", // strings are applied to diagnostic code (or legacyCode) against all files
    { "codes": ["code-1", "code-2"]}, // an object is allowed with a list of codes to applied against all files
    { "files": "some/glob/**" }, // a file list will suppress all diagnostics in matched files
    { "files": "other/glob/**", "codes": ["some-code"]}, //suppress specific codes in matched files
    { "files": [              //files can be an array of globs
            "other/glob/**",
            "other2/glob/**"
       ],
       "codes": ["some-code"]
    },
    { "files": [{ "src": "yet/another/glob/**"}] }, //file listings in an array can be a object that references src path
    { "files": [{ "dest": "yet/another/glob/**"}] } //file listings in an array can be a object that references dest path
]}

```

Additionally, I went through all the diagnostics and assigned a human-readable `code` property to them, which can also be used for any diagnostic filtering. The previous (numerical) code property was renamed to `legacyCode`






markwpearce: @josephjunker Please take a look at this PR -- you did a lot of work on DiagnosticFiltering and I hope that's respected here.

josephjunker: @markwpearce Looks good to me!

Patch:
diff --git a/docs/bsconfig.md b/docs/bsconfig.md
index 01594bca..b899430d 100644
--- a/docs/bsconfig.md
+++ b/docs/bsconfig.md
@@ -46,7 +46,7 @@ BrighterScript only: will automatically import a script at transpile-time for a
 
 Type: `string`
 
-Override the destination directory for the bslib.brs file.  Use this if you want to customize where the bslib.brs file is located in the staging directory.  Note that using a location outside of `source` will break scripts inside `source` that depend on bslib.brs.
+Override the destination directory for the bslib.brs file. Use this if you want to customize where the bslib.brs file is located in the staging directory. Note that using a location outside of `source` will break scripts inside `source` that depend on bslib.brs.
 
 Defaults to `source`.
 
@@ -70,34 +70,60 @@ If `true`, after a successful build, the project will be deployed to the Roku sp
 
 ## `diagnosticFilters`
 
-Type: `Array<string | number | {src: string; codes: number[]}`
+Type: `Array<string | number | {files: string | Array<string | {src: string} | {dest: string}>; codes?: Array<number | string>}`
 
 A list of filters used to hide diagnostics.
 
-- A `string` value should be a relative-to-root-dir or absolute file path or glob pattern of the files that should be excluded. Any file matching this pattern will have all diagnostics supressed. These file paths refer to the location of the source files pre-compilation and are relative to [`rootDir`](#rootdir). Absolute file paths may be used as well.
-    - A file glob may be prefixed with `!` to make it a negative pattern which "un-ignores" the files it matches. (See examples below).
-- A `number` value should be a diagnostic code. This will supress all diagnostics with that code for the whole project.
-- An object can also be provided to filter specific diagnostic codes for a file pattern. For example,
+- A `string` value should be a diagnostic code. This will supress all diagnostics with that code for the whole project. For example:
 
-    ```jsonc
-    "diagnosticFilters": [{
-        "src": "vendor/**/*",
-        "codes": [1000, 1011] //ignore these specific codes from vendor libraries
-    }]
-    ```
+  ```jsonc
+  "diagnosticFilters": [
+      "cannot-find-name",
+      "local-var-function-shadow",
+      "mismatch-argument-count"
+  ]
+  ```
+
+- An object can also be provided to filter specific diagnostic codes for a file pattern. If no `files` property is included, any diagnostic that matches the values in the `codes` will b e suppressed. If a `string` if given for the `files` property, it is treated as a relative-to-root-dir or absolute file path or glob pattern of the files that should be excluded. These file paths refer to the location of the source files pre-compilation and are relative to [`rootDir`](#rootdir). Absolute file paths may be used as well. For example,
+
+  ```jsonc
+  "diagnosticFilters": [{
+      "files": "vendor/**/*",
+      "codes": ["cannot-find-name", "mismatch-argument-count"] //ignore these specific codes from vendor libraries
+  }]
+  ```
+
+- If an object is provided, the `files` property could also be an array, providing either a series of globs, or a specific set of files that match _either_ their `src` or `dest` paths. For example,
+
+  ```jsonc
+  "diagnosticFilters": [{
+      "files": [
+          "vendor/**/*", // all vendor files will be suppressed
+          { "src": "themes/theme1/**/*"}, // all files coming from `themes/theme1/` will be suppressed
+          { "dest": "source/common/**/*"}, // all files from `source/common/` will be suppressed
+      ]
+      "codes": ["cannot-find-name", "mismatch-argument-count"] //ignore these specific codes
+  }]
+  ```
+
+- A file glob may be prefixed with `!` to make it a negative pattern which "un-ignores" the files it matches. (See examples below).
 
 Defaults to `undefined`.
 
 If a child bsconfig extends from a parent bsconfig, and both bsconfigs specify `diagnosticFilters`, the parent bsconfig's `diagnosticFilters` field will be completely overwritten.
 
+
+**Note:** In Brighterscript v0, all diagnostics used a numerical code. Using those legacy codes will still work, but is *deprecated*. Therefore, it is possible to use `number` values instead of string codes. Using a number (or a string representation of a number) matches all diagnostic codes that have that legacy code. For example, an entry of `1234` (or `"1234"`) would suppress any diagnostic with legacy code `1234`.
+
+
 ### Negative patterns in `diagnosticFilters`
 
 A negative pattern can be used to un-ignore some files or codes which were previously ignored. For example,
 
 ```jsonc
 "diagnosticFilters": [
-    { "src": "vendor/**/*" }, //ignore all codes from vendor libraries
-    { "src": "!vendor/unreliable/**/*" } //EXCEPT do show errors from this one specific library
+    { "files": "vendor/**/*" }, //ignore all codes from vendor libraries
+    { "files": "!vendor/unreliable/**/*" } //EXCEPT do show errors from this one specific library
 ]
 ```
 
@@ -105,8 +131,8 @@ A specific error code can be unignored in multiple places by using a pattern whi
 
 ```jsonc
 "diagnosticFilters": [
-    { "src": "vendor/**/*" }, //ignore all errors from vendor libraries
-    { "src": "!*/**/*", "codes": [1000] } //EXCEPT do show this particular code everywhere
+    { "files": "vendor/**/*" }, //ignore all errors from vendor libraries
+    { "files": "!*/**/*", "codes": ["name-collision"] } //EXCEPT do show this particular code everywhere
 ]
 ```
 
@@ -126,7 +152,7 @@ A map of error codes and severity levels that will override diagnostics' severit
 
 ```jsonc
 "diagnosticSeverityOverrides": {
-    "1011": "error",   //raise a warning to an error
+    "local-var-function-shadow": "error",   //raise a warning to an error
     "LINT1001": "warn" //oops we have lots of those to fix... later
 }
 ```
@@ -156,47 +182,51 @@ Child config properties completely replace parent config properties. For example
 All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
 
 ### Optional `extends` and `project`
+
 There are situations where you want to store some compiler settings in a config file, but not fail if that config file doesn't exist. To do this, you can denote that your [`extends`](#extends) or [`project`](#project) path is optional by prefixing it with a question mark (`?`). For example:
 
 - **bsconfig.json** `extends`
-    ```json
-    {
-      "extends": "?path/to/optional/bsconfig.json"
-    }
-    ```
+  ```json
+  {
+    "extends": "?path/to/optional/bsconfig.json"
+  }
+  ```
 - CLI "extends"
-    ```
-    bsc --extends "?path/to/optional/bsconfig.json"
-    ```
+
+  ```
+  bsc --extends "?path/to/optional/bsconfig.json"
+  ```
 
 - CLI `project` argument
-    ```
-    bsc --project "?path/to/optional/bsconfig.json"
-    ```
+  ```
+  bsc --project "?path/to/optional/bsconfig.json"
+  ```
 - Node.js API `extends`
-    ```
-    var programBuilder = new ProgramBuilder({
-        "extends": "?path/to/optional/bsconfig.json"
-    });
-    ```
+  ```
+  var programBuilder = new ProgramBuilder({
+      "extends": "?path/to/optional/bsconfig.json"
+  });
+  ```
 - Node.js API `project`
-    ```
-    var programBuilder = new ProgramBuilder({
-        "project": "?path/to/optional/bsconfig.json"
-    });
-    ```
+  ```
+  var programBuilder = new ProgramBuilder({
+      "project": "?path/to/optional/bsconfig.json"
+  });
+  ```
 
 ## `files`
 
 Type:
+
 ```typescript
 Array<
-  string |
-  string[] |
-  {
-    src: string | string[],
-    dest: string
-  }>
+  | string
+  | string[]
+  | {
+      src: string | string[];
+      dest: string;
+    }
+>;
 ```
 
 The files array is how you specify what files are included in your project. Any strings found in the files array must be relative to rootDir, and are used as include filters, meaning that if a file matches the pattern, it is included.
@@ -205,12 +235,7 @@ For most standard projects, the default files array should work just fine:
 
 ```jsonc
 {
-    "files": [
-        "source/**/*",
-        "components/**/*",
-        "images/**/*",
-        "manifest"
-    ]
+  "files": ["source/**/*", "components/**/*", "images/**/*", "manifest"]
 }
 ```
 
@@ -239,10 +264,7 @@ You can exclude files from the output by prefixing your file patterns with "!".
 
 ```jsonc
 {
-    "files": [
-        "source/**/*",
-        "!source/some/unwanted/file.brs"
-    ]
+  "files": ["source/**/*", "!source/some/unwanted/file.brs"]
 }
 ```
 
@@ -256,13 +278,13 @@ Patterns may not reference files outside of [`rootDir`](#rootdir) unless the `{
 
 ```jsonc
 {
-    "rootDir": "C:/projects/CatVideoPlayer",
-    "files": [
-        "source/main.brs",
+  "rootDir": "C:/projects/CatVideoPlayer",
+  "files": [
+    "source/main.brs",
 
-        //NOT allowed because it navigates outside the rootDir
-        "../common/promise.brs"
-    ]
+    //NOT allowed because it navigates outside the rootDir
+    "../common/promise.brs"
+  ]
 }
 ```
 
@@ -280,17 +302,17 @@ The object structure is as follows:
 
 ```typescript
 {
-    /**
-     * A glob pattern string or file path, or an array of glob pattern strings and/or file paths.
-     * These can be relative paths or absolute paths.
-     * All non-absolute paths are resolved relative to the rootDir
-     */
-    src: Array<string | string[]>;
-    /**
-     * The relative path to the location in the output folder where the files should be placed,
-     * relative to the root of the output folder
-     */
-    dest: string | undefined
+  /**
+   * A glob pattern string or file path, or an array of glob pattern strings and/or file paths.
+   * These can be relative paths or absolute paths.
+   * All non-absolute paths are resolved relative to the rootDir
+   */
+  src: Array<string | string[]>;
+  /**
+   * The relative path to the location in the output folder where the files should be placed,
+   * relative to the root of the output folder
+   */
+  dest: string | undefined;
 }
 ```
 
@@ -318,14 +340,14 @@ An example of combining regular and advanced file patterns:
 
 ```jsonc
 {
-    "rootDir": "C:/projects/CatVideoPlayer",
-    "files": [
-        "source/main.brs",
-        {
-          "src": "../common/promise.brs",
-          "dest": "source/common"
-        }
-    ]
+  "rootDir": "C:/projects/CatVideoPlayer",
+  "files": [
+    "source/main.brs",
+    {
+      "src": "../common/promise.brs",
+      "dest": "source/common"
+    }
+  ]
 }
 ```
 
@@ -337,15 +359,15 @@ For example, if you have a base project and a child project that wants to overri
 
 ```jsonc
 {
-    "files": [
-        {
-            //copy all files from the base project
-            "src": "../BaseProject/**/*"
-        },
-        // Override "../BaseProject/themes/theme.brs"
-        // with "${rootDir}/themes/theme.brs"
-        "themes/theme.brs"
-    ]
+  "files": [
+    {
+      //copy all files from the base project
+      "src": "../BaseProject/**/*"
+    },
+    // Override "../BaseProject/themes/theme.brs"
+    // with "${rootDir}/themes/theme.brs"
+    "themes/theme.brs"
+  ]
 }
 ```
 
diff --git a/src/BsConfig.ts b/src/BsConfig.ts
index 83786b2e..6777ef59 100644
--- a/src/BsConfig.ts
+++ b/src/BsConfig.ts
@@ -128,7 +128,7 @@ export interface BsConfig {
     /**
      * A list of filters used to exclude diagnostics from the output
      */
-    diagnosticFilters?: Array<number | string | { src: string; codes: (number | string)[] } | { src: string } | { codes: (number | string)[] }>;
+    diagnosticFilters?: Array<string | number | { files?: string | Array<string | { src: string } | { dest: string }>; codes?: Array<number | string> }>;
 
     /**
      * Specify what diagnostic types should be printed to the console. Defaults to 'warn'
diff --git a/src/DiagnosticFilterer.spec.ts b/src/DiagnosticFilterer.spec.ts
index f76bd1a4..cec00539 100644
--- a/src/DiagnosticFilterer.spec.ts
+++ b/src/DiagnosticFilterer.spec.ts
@@ -1,6 +1,7 @@
 import { expect } from './chai-config.spec';
 import { DiagnosticFilterer } from './DiagnosticFilterer';
 import type { BsDiagnostic } from './interfaces';
+import { Program } from './Program';
 import util, { standardizePath as s } from './util';
 import { createSandbox } from 'sinon';
 const sinon = createSandbox();
@@ -12,14 +13,15 @@ describe('DiagnosticFilterer', () => {
     let options = {
         rootDir: rootDir,
         diagnosticFilters: [
+            'codename',
             //ignore these codes globally
             { codes: [1, 2, 3, 'X4'] },
             //ignore all codes from lib
-            { src: 'lib/**/*.brs' },
+            { files: 'lib/**/*.brs' },
             //ignore all codes from `packages` with absolute path
-            { src: `${rootDir}/packages/**/*.brs` },
+            { files: `${rootDir}/packages/**/*.brs` },
             //ignore specific codes for main.brs
-            { src: 'source/main.brs', codes: [4] }
+            { files: 'source/main.brs', codes: [4] }
         ]
     };
 
@@ -86,24 +88,34 @@ describe('DiagnosticFilterer', () => {
             ).to.eql([11, 12, 13, 'X14']);
         });
 
+
+        it('works with single file src glob', () => {
+            expect(
+                filterer.filter(options, [
+                    getDiagnostic('codename', `${rootDir}/source/main.brs`) //remove
+                ]).map(x => x.code)
+            ).to.eql([]);
+        });
+
         describe('with negative globs', () => {
             let optionsWithNegatives = {
                 rootDir: rootDir,
                 diagnosticFilters: [
                     //ignore these codes globally
-                    { codes: [1, 2] },
+                    { codes: [1, 2, 'codename'] },
                     3,
                     4,
+                    'codename',
                     //ignore all codes from lib
-                    { src: 'lib/**/*.brs' },
+                    { files: 'lib/**/*.brs' },
                     //un-ignore specific errors from lib/special
-                    { src: '!lib/special/**/*.brs', codes: [1, 2, 3] },
+                    { files: '!lib/special/**/*.brs', codes: [1, 2, 3, 'codename'] },
                     //re-ignore errors from one extra special file
-                    { src: 'lib/special/all-reignored.brs' },
+                    { files: 'lib/special/all-reignored.brs' },
                     //un-ignore all codes from third special file
-                    { src: '!lib/special/all-unignored.brs' },
+                    { files: '!lib/special/all-unignored.brs' },
                     //un-ignore code 5 globally
-                    { src: '!*/**/*', codes: [5] },
+                    { files: '!*/**/*', codes: [5] },
                     //re-ignore code 10 globally, overriding previous unignores
                     { codes: [10] }
                 ]
@@ -180,7 +192,7 @@ describe('DiagnosticFilterer', () => {
         it('handles standard diagnostic filters', () => {
             expect(
                 filterer.getDiagnosticFilters({
-                    diagnosticFilters: [{ src: 'file.brs', codes: [1, 2, 'X3'] }]
+                    diagnosticFilters: [{ files: 'file.brs', codes: [1, 2, 'X3'] }]
                 })
             ).to.eql([{ src: 'file.brs', codes: [1, 2, 'X3'], isNegative: false }]);
         });
@@ -188,7 +200,7 @@ describe('DiagnosticFilterer', () => {
         it('handles string-only diagnostic filter object', () => {
             expect(
                 filterer.getDiagnosticFilters({
-                    diagnosticFilters: [{ src: 'file.brs' }]
+                    diagnosticFilters: [{ files: 'file.brs' }]
                 })
             ).to.eql([{ src: 'file.brs', isNegative: false }]);
         });
@@ -204,9 +216,9 @@ describe('DiagnosticFilterer', () => {
         it('handles string diagnostic filter', () => {
             expect(
                 filterer.getDiagnosticFilters({
-                    diagnosticFilters: ['file.brs']
+                    diagnosticFilters: ['cannot-find-name']
                 })
-            ).to.eql([{ src: 'file.brs', isNegative: false }]);
+            ).to.eql([{ codes: ['cannot-find-name'], isNegative: false }]);
         });
 
         it('converts ignoreErrorCodes to diagnosticFilters', () => {
@@ -217,19 +229,11 @@ describe('DiagnosticFilterer', () => {
             ]);
         });
 
-        it('handles negative globs in bare strings', () => {
-            expect(filterer.getDiagnosticFilters({
-                diagnosticFilters: ['!file.brs']
-            })).to.eql([
-                { src: 'file.brs', isNegative: true }
-            ]);
-        });
-
         it('handles negative globs in objects', () => {
             expect(filterer.getDiagnosticFilters({
                 diagnosticFilters: [
                     {
-                        src: '!file.brs'
+                        files: '!file.brs'
                     }
                 ]
             })).to.eql([
@@ -241,7 +245,7 @@ describe('DiagnosticFilterer', () => {
             expect(filterer.getDiagnosticFilters({
                 diagnosticFilters: [
                     {
-                        src: '!file.brs',
+                        files: '!file.brs',
                         codes: [1, 2, 3]
                     }
                 ]
@@ -251,6 +255,54 @@ describe('DiagnosticFilterer', () => {
         });
     });
 
+    describe('filtering by dest', () => {
+        it('will filter by a files destPath', () => {
+            const program = new Program({ rootDir: rootDir });
+            program.setFile('source/common.brs', '');
+            program.setFile({ src: `${rootDir}/source/utils.brs`, dest: `source/remove/utils.brs` }, '');
+            program.setFile({ src: `${rootDir}/components/utils.brs`, dest: `source/remove/utils2.brs` }, '');
+
+            const resultDiagnostics = filterer.filter({
+                rootDir: rootDir,
+                diagnosticFilters: [
+                    {
+                        files: [
+                            { dest: 'source/remove/**/*.*' }
+                        ],
+                        codes: ['diagCode']
+                    }
+                ]
+            }, [
+                getDiagnostic('diagCode', `${rootDir}/source/common.brs`), //keep
+                getDiagnostic('diagCode', `${rootDir}/source/utils.brs`, `${rootDir}/source/remove/utils.brs`), //remove
+                getDiagnostic('diagCode', `${rootDir}/components/utils.brs`, `${rootDir}/source/remove/utils2.brs`) //remove
+            ], program);
+            expect(resultDiagnostics.map(x => x.code)).to.eql(['diagCode']);
+            expect(resultDiagnostics.map(x => x.location.uri)).to.eql([util.pathToUri(s`${rootDir}/source/common.brs`)]);
+        });
+
+        it('respects order of ignores with negative globs', () => {
+            const resultDiagnostics = filterer.filter({
+                rootDir: rootDir,
+                diagnosticFilters: [{
+                    files: [
+                        { dest: 'source/**/*.*' } //ignore diagCode in files with destPath in /source/remove
+                    ],
+                    codes: ['diagCode']
+                }, {
+                    files: '!**/*.*', //unignore diagCode everywhere
+                    codes: ['diagCode']
+                }
+                ]
+            }, [
+                getDiagnostic('diagCode', `${rootDir}/source/common.brs`), //keep
+                getDiagnostic('diagCode', `${rootDir}/source/utils.brs`, `${rootDir}/source/remove/utils.brs`), //remove
+                getDiagnostic('diagCode', `${rootDir}/components/utils.brs`, `${rootDir}/source/remove/utils2.brs`) //remove
+            ]);
+            expect(resultDiagnostics.map(x => x.code)).to.eql(['diagCode', 'diagCode', 'diagCode']);
+        });
+    });
+
     it('only filters by file once per unique file (case-insensitive)', () => {
         const stub = sinon.stub(filterer as any, 'filterFile').returns(null);
         filterer.filter(options, [
@@ -259,8 +311,16 @@ describe('DiagnosticFilterer', () => {
             getDiagnostic(3, s`${rootDir}/source/common2.brs`),
             getDiagnostic(4, s`${rootDir}/source/Common2.brs`)
         ]);
-        expect(stub.callCount).to.eql(2);
+        const expectedCallCount = options.diagnosticFilters.reduce((acc, filter) => {
+            if (typeof filter === 'object' && 'files' in (filter as any)) {
+                return acc;
+            }
+            return acc + 1;
+        }, 0);
+        expect(stub.callCount).to.eql(expectedCallCount * 2); // 2 times for 'codename', 2 times for { codes: [1, 2, 3, 'X4'] }
         expect(stub.getCalls().map(x => x.args[1].toLowerCase())).to.eql([
+            util.pathToUri(s`${rootDir.toLowerCase()}/source/common1.brs`).toLowerCase(),
+            util.pathToUri(s`${rootDir.toLowerCase()}/source/common2.brs`).toLowerCase(),
             util.pathToUri(s`${rootDir.toLowerCase()}/source/common1.brs`).toLowerCase(),
             util.pathToUri(s`${rootDir.toLowerCase()}/source/common2.brs`).toLowerCase()
         ]);
@@ -268,7 +328,8 @@ describe('DiagnosticFilterer', () => {
 
 });
 
-function getDiagnostic(code: number | string, srcPath: string) {
+function getDiagnostic(code: number | string, srcPath: string, destPath?: string) {
+    destPath = destPath ?? srcPath;
     return {
         location: {
             uri: util.pathToUri(s`${srcPath}`)
diff --git a/src/DiagnosticFilterer.ts b/src/DiagnosticFilterer.ts
index db84fce5..91e4b604 100644
--- a/src/DiagnosticFilterer.ts
+++ b/src/DiagnosticFilterer.ts
@@ -4,6 +4,7 @@ import * as minimatch from 'minimatch';
 import type { BsConfig } from './BsConfig';
 import util, { standardizePath as s } from './util';
 import { URI } from 'vscode-uri';
+import type { Program } from './Program';
 
 interface DiagnosticWithSuppression {
     diagnostic: BsDiagnostic;
@@ -12,28 +13,31 @@ interface DiagnosticWithSuppression {
 
 interface NormalizedFilter {
     src?: string;
+    dest?: string;
     codes?: (number | string)[];
     isNegative: boolean;
 }
 
 export class DiagnosticFilterer {
     private byFile: Record<string, DiagnosticWithSuppression[]>;
+    private fileDestSrcUriMap: Record<string, string>;
     private filters: NormalizedFilter[] | undefined;
     private rootDir: string | undefined;
 
 
     constructor() {
         this.byFile = {};
+        this.fileDestSrcUriMap = {};
     }
 
     /**
      * Filter a list of diagnostics based on the provided filters
      */
-    public filter(options: BsConfig, diagnostics: BsDiagnostic[]) {
+    public filter(options: BsConfig, diagnostics: BsDiagnostic[], program?: Program) {
         this.filters = this.getDiagnosticFilters(options);
         this.rootDir = options.rootDir;
 
-        this.groupByFile(diagnostics);
+        this.groupByFile(diagnostics, program);
 
         for (let filter of this.filters) {
             this.filterAllFiles(filter);
@@ -42,6 +46,7 @@ export class DiagnosticFilterer {
 
         //clean up
         this.byFile = {};
+        this.fileDestSrcUriMap = {};
         delete this.rootDir;
         delete this.filters;
 
@@ -70,9 +75,9 @@ export class DiagnosticFilterer {
     /**
      * group the diagnostics by file
      */
-    private groupByFile(diagnostics: BsDiagnostic[]) {
+    private groupByFile(diagnostics: BsDiagnostic[], program?: Program) {
         this.byFile = {};
-
+        this.fileDestSrcUriMap = {};
         for (let diagnostic of diagnostics) {
             const fileUri = diagnostic?.location?.uri ?? 'invalid-uri';
             //skip diagnostics that have issues
@@ -88,15 +93,24 @@ export class DiagnosticFilterer {
                 diagnostic: diagnostic,
                 isSuppressed: false
             });
+
+            if (program) {
+                const fileForDiagnostic = program.getFile(diagnostic.location?.uri);
+                if (fileForDiagnostic) {
+                    const lowerDestPath = fileForDiagnostic.destPath.toLowerCase();
+                    this.fileDestSrcUriMap[lowerDestPath] = diagnostic.location?.uri;
+                }
+            }
         }
     }
 
     private filterAllFiles(filter: NormalizedFilter) {
         let matchedFileUris: string[];
 
-        //if there's a src, match against all files
         if (filter.src) {
-            //prepend rootDir to src if the filter is a relative path
+            //if there's a src, match against all files
+
+            //prepend rootDir to src if the filter is not a relative path
             let src = s(
                 path.isAbsolute(filter.src) ? filter.src : `${this.rootDir}/${filter.src}`
             );
@@ -106,7 +120,17 @@ export class DiagnosticFilterer {
                 nocase: true
             }).map(src => util.pathToUri(src).toLowerCase());
 
-            //there is no src; this applies to all files
+        } else if (filter.dest) {
+            // applies to file dest location
+
+            // search against the set of file destinations
+            const byFileDests = Object.keys(this.fileDestSrcUriMap);
+            matchedFileUris = minimatch.match(byFileDests, filter.dest, {
+                nocase: true
+            }).map((destPath) => {
+                return this.fileDestSrcUriMap[destPath]?.toLowerCase();
+            });
+
         } else {
             matchedFileUris = Object.keys(this.byFile);
         }
@@ -118,6 +142,9 @@ export class DiagnosticFilterer {
     }
 
     private filterFile(filter: NormalizedFilter, fileUri: string) {
+        if (!fileUri) {
+            return;
+        }
         //if the filter is negative, we're turning diagnostics on
         //if the filter is not negative we're turning diagnostics off
         const isSuppressing = !filter.isNegative;
@@ -132,7 +159,10 @@ export class DiagnosticFilterer {
         } else {
             let fileDiagnostics = this.byFile[lowerFileUri];
             for (const diagnostic of fileDiagnostics) {
-                if (filter.codes.includes(diagnostic.diagnostic.code!)) {
+                if (filter.codes.includes(diagnostic.diagnostic.code!) ||
+                    (diagnostic.diagnostic.legacyCode &&
+                        (filter.codes.includes(diagnostic.diagnostic.legacyCode) ||
+                            filter.codes.includes(diagnostic.diagnostic.legacyCode.toString())))) {
                     diagnostic.isSuppressed = isSuppressing;
                 }
             }
@@ -155,7 +185,7 @@ export class DiagnosticFilterer {
         }
 
         for (let filter of diagnosticFilters) {
-            if (typeof filter === 'number') {
+            if (typeof filter === 'number' || typeof filter === 'string') {
                 result.push({
                     codes: [filter],
                     isNegative: false
@@ -163,24 +193,13 @@ export class DiagnosticFilterer {
                 continue;
             }
 
-            if (typeof filter === 'string') {
-                const isNegative = filter.startsWith('!');
-                const trimmedFilter = isNegative ? filter.slice(1) : filter;
-
-                result.push({
-                    src: trimmedFilter,
-                    isNegative: isNegative
-                });
-                continue;
-            }
-
             //filter out bad inputs
             if (!filter || typeof filter !== 'object') {
                 continue;
             }
 
             //code-only filter
-            if ('codes' in filter && !('src' in filter) && Array.isArray(filter.codes)) {
+            if ('codes' in filter && !('files' in filter) && Array.isArray(filter.codes)) {
                 result.push({
                     codes: filter.codes,
                     isNegative: false
@@ -188,24 +207,67 @@ export class DiagnosticFilterer {
                 continue;
             }
 
-            if ('src' in filter) {
-                const isNegative = filter.src.startsWith('!');
-                const trimmedFilter = isNegative ? filter.src.slice(1) : filter.src;
-
-                if ('codes' in filter) {
-                    result.push({
-                        src: trimmedFilter,
-                        codes: filter.codes,
-                        isNegative: isNegative
-                    });
-                } else {
-                    result.push({
-                        src: trimmedFilter,
-                        isNegative: isNegative
-                    });
+            if ('files' in filter) {
+                if (typeof filter.files === 'string') {
+                    result.push(this.getNormalizedFilter(filter.files, filter));
+                    continue;
+                }
+
+                if (Array.isArray(filter.files)) {
+                    for (const fileIdentifier of filter.files) {
+                        if (typeof fileIdentifier === 'string') {
+                            result.push(this.getNormalizedFilter(fileIdentifier, filter));
+                            continue;
+                        }
+                        if (typeof fileIdentifier === 'object') {
+                            if ('src' in fileIdentifier) {
+                                result.push(this.getNormalizedFilter(fileIdentifier.src, filter));
+                                continue;
+                            }
+                            if ('dest' in fileIdentifier) {
+                                result.push(this.getNormalizedFilter(fileIdentifier.dest, filter, 'dest'));
+                                continue;
+                            }
+                        }
+                    }
                 }
             }
         }
         return result;
     }
+
+
+    private getNormalizedFilter(fileGlob: string, filter: { files: string } | { codes?: (number | string)[] }, locationKey: 'src' | 'dest' = 'src'): NormalizedFilter {
+        const isNegative = fileGlob.startsWith('!');
+        const trimmedFilter = isNegative ? fileGlob.slice(1) : fileGlob;
+        if (locationKey === 'src') {
+            if ('codes' in filter && Array.isArray(filter.codes)) {
+                return {
+                    src: trimmedFilter,
+                    codes: filter.codes,
+                    isNegative: isNegative
+                };
+            } else {
+                return {
+                    src: trimmedFilter,
+                    isNegative: isNegative
+                };
+            }
+        } else {
+            // dest
+            if ('codes' in filter && Array.isArray(filter.codes)) {
+                return {
+                    dest: trimmedFilter,
+                    codes: filter.codes,
+                    isNegative: isNegative
+                };
+            } else {
+                return {
+                    dest: trimmedFilter,
+                    isNegative: isNegative
+                };
+            }
+        }
+
+    }
 }
diff --git a/src/DiagnosticManager.ts b/src/DiagnosticManager.ts
index 6607aca3..140788b2 100644
--- a/src/DiagnosticManager.ts
+++ b/src/DiagnosticManager.ts
@@ -135,14 +135,17 @@ export class DiagnosticManager {
      * Determine whether this diagnostic should be supressed or not, based on brs comment-flags
      */
     public isDiagnosticSuppressed(diagnostic: BsDiagnostic) {
-        const diagnosticCode = typeof diagnostic.code === 'string' ? diagnostic.code.toLowerCase() : diagnostic.code;
+        const diagnosticCode = typeof diagnostic.code === 'string' ? diagnostic.code.toLowerCase() : diagnostic.code?.toString() ?? undefined;
+        const diagnosticLegacyCode = typeof diagnostic.legacyCode === 'string' ? diagnostic.legacyCode.toLowerCase() : diagnostic.legacyCode;
         const file = this.program?.getFile(diagnostic.location?.uri);
 
         for (let flag of file?.commentFlags ?? []) {
             //this diagnostic is affected by this flag
             if (diagnostic.location.range && util.rangeContains(flag.affectedRange, diagnostic.location.range.start)) {
                 //if the flag acts upon this diagnostic's code
-                if (flag.codes === null || (diagnosticCode !== undefined && flag.codes.includes(diagnosticCode))) {
+                const diagCodeSuppressed = (diagnosticCode !== undefined && flag.codes?.includes(diagnosticCode)) ||
+                    (diagnosticLegacyCode !== undefined && flag.codes?.includes(diagnosticLegacyCode));
+                if (flag.codes === null || diagCodeSuppressed) {
                     return true;
                 }
             }
@@ -155,7 +158,7 @@ export class DiagnosticManager {
         let filteredDiagnostics = this.diagnosticFilterer.filter({
             ...this.options ?? {},
             rootDir: this.options?.rootDir
-        }, diagnostics);
+        }, diagnostics, this.program);
         return filteredDiagnostics;
     }
 
diff --git a/src/DiagnosticMessages.spec.ts b/src/DiagnosticMessages.spec.ts
index fa54bfc9..a34506fe 100644
--- a/src/DiagnosticMessages.spec.ts
+++ b/src/DiagnosticMessages.spec.ts
@@ -1,19 +1,56 @@
 import { expect } from './chai-config.spec';
 
-import { DiagnosticMessages } from './DiagnosticMessages';
+import { DiagnosticCodeRegex, DiagnosticMessages } from './DiagnosticMessages';
 
 describe('DiagnosticMessages', () => {
+    it('has unique legacyCode for each message', () => {
+        let codes = {};
+        for (let key in DiagnosticMessages) {
+            let func = DiagnosticMessages[key];
+            let obj = func('', '', '', '', '', '', '', '', '');
+            if (obj.legacyCode === undefined) {
+                // does not have a legacy code
+                continue;
+            }
+
+            //if another message already has this code
+            if (!codes[obj.legacyCode]) {
+                codes[obj.legacyCode] = key;
+            } else {
+                expect(codes[obj.legacyCode]).to.equal(key, 'Two diagnostic messages share the same legacy Code');
+            }
+        }
+    });
+
     it('has unique code for each message', () => {
         let codes = {};
         for (let key in DiagnosticMessages) {
+            if (key.startsWith('__unused')) {
+                // ignore unused diagnostics
+                continue;
+            }
             let func = DiagnosticMessages[key];
             let obj = func('', '', '', '', '', '', '', '', '');
+            const diagCode: string = obj.code ?? '';
+            expect(diagCode).to.not.equal('', `Diagnostic name is empty - ${key}`);
+            expect(diagCode.toLowerCase()).to.equal(obj.code, `Diagnostic name has capitals - ${key}`);
+            expect(diagCode.indexOf(' ')).to.equal(-1, `Diagnostic name has space - ${key}`);
+            expect(DiagnosticCodeRegex.test(diagCode)).to.equal(true, `Diagnostic name does not match regex - ${key}`);
             //if another message already has this code
             if (!codes[obj.code]) {
                 codes[obj.code] = key;
             } else {
-                expect(codes[obj.code]).to.equal(key, 'Two diagnostic messages share the same error code');
+                expect(codes[obj.code]).to.equal(key, 'Two diagnostic messages share the same error codes');
             }
         }
     });
+
+    it('properly formats expected terminator diagnostics', () => {
+        let diag = DiagnosticMessages.expectedTerminator(['end if', 'else if', 'else'], 'then', 'block');
+        expect(diag.message).to.equal(`Expected 'end if', 'else if' or 'else' to terminate 'then' block`);
+        diag = DiagnosticMessages.expectedTerminator('end try', 'try-catch');
+        expect(diag.message).to.equal(`Expected 'end try' to terminate 'try-catch' statement`);
+        diag = DiagnosticMessages.expectedTerminator(['one', 'two'], 'something');
+        expect(diag.message).to.equal(`Expected 'one' or 'two' to terminate 'something' statement`);
+    });
 });
diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts
index a02d2ac5..d7d8248e 100644
--- a/src/DiagnosticMessages.ts
+++ b/src/DiagnosticMessages.ts
@@ -5,6 +5,9 @@ import { TokenKind } from './lexer/TokenKind';
 import util from './util';
 import { SymbolTypeFlag } from './SymbolTypeFlag';
 
+
+export const DiagnosticCodeRegex = /^[a-z](?:[a-z0-9]*(?:-[a-z0-9]+)*)*$/;
+
 /**
  * An object that keeps track of all possible error messages.
  */
@@ -12,8 +15,9 @@ export let DiagnosticMessages = {
     //this one won't be used much, we just need a catchall object for the code since we pass through the message from the parser
     genericParserMessage: (message: string) => ({
         message: message,
-        code: 1000,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1000,
+        severity: DiagnosticSeverity.Error,
+        code: 'generic-parser-message'
     }),
     /**
      *
@@ -24,711 +28,907 @@ export let DiagnosticMessages = {
      */
     cannotFindName: (name: string, fullName?: string, typeName?: string, typeDescriptor = 'type') => ({
         message: `Cannot find name '${name}'${typeName ? ` for ${typeDescriptor} '${typeName}'` : ''}`,
-        code: 1001,
+        legacyCode: 1001,
         data: {
             name: name,
             fullName: fullName ?? name,
             typeName: typeName ? typeName : undefined
         },
-        severity: DiagnosticSeverity.Error
+        severity: DiagnosticSeverity.Error,
+        code: 'cannot-find-name'
     }),
     mismatchArgumentCount: (expectedCount: number | string, actualCount: number) => ({
         message: `Expected ${expectedCount} arguments, but got ${actualCount}.`,
-        code: 1002,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1002,
+        severity: DiagnosticSeverity.Error,
+        code: 'incorrect-argument-count'
     }),
     duplicateFunctionImplementation: (functionName: string) => ({
         message: `Duplicate function implementation for '${functionName}'.`,
-        code: 1003,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1003,
+        severity: DiagnosticSeverity.Error,
+        code: 'duplicate-function'
     }),
     referencedFileDoesNotExist: () => ({
         message: `Referenced file does not exist.`,
-        code: 1004,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1004,
+        severity: DiagnosticSeverity.Error,
+        code: 'file-not-found'
     }),
     xmlComponentMissingComponentDeclaration: () => ({
         message: `Missing a component declaration.`,
-        code: 1005,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1005,
+        severity: DiagnosticSeverity.Error,
+        code: 'missing-component-element'
     }),
     xmlComponentMissingNameAttribute: () => ({
         message: `Component must have a name attribute.`,
-        code: 1006,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1006,
+        severity: DiagnosticSeverity.Error,
+        code: 'missing-name-attribute'
     }),
     xmlComponentMissingExtendsAttribute: () => ({
         message: `Component is mising "extends" attribute and will automatically extend "Group" by default`,
-        code: 1007,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1007,
+        severity: DiagnosticSeverity.Warning,
+        code: 'missing-extends-attribute'
     }),
-    xmlGenericParseError: (message: string) => ({
+    syntaxError: (message: string) => ({
         //generic catchall xml parse error
         message: message,
-        code: 1008,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1008,
+        severity: DiagnosticSeverity.Error,
+        code: 'syntax-error'
     }),
     unnecessaryScriptImportInChildFromParent: (parentComponentName: string) => ({
         message: `Unnecessary script import: Script is already imported in ancestor component '${parentComponentName}'.`,
-        code: 1009,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1009,
+        severity: DiagnosticSeverity.Warning,
+        code: 'redundant-import'
     }),
     overridesAncestorFunction: (callableName: string, currentScopeName: string, parentFilePath: string, parentScopeName: string) => ({
         message: `Function '${callableName}' included in '${currentScopeName}' overrides function in '${parentFilePath}' included in '${parentScopeName}'.`,
-        code: 1010,
-        severity: DiagnosticSeverity.Hint
+        legacyCode: 1010,
+        severity: DiagnosticSeverity.Hint,
+        code: 'overrides-ancestor-function'
     }),
     localVarFunctionShadowsParentFunction: (scopeName: 'stdlib' | 'scope') => ({
         message: `Local variable function has same name as ${scopeName} function and will never be called.`,
-        code: 1011,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1011,
+        severity: DiagnosticSeverity.Warning,
+        code: 'variable-shadows-function'
     }),
     scriptImportCaseMismatch: (correctFilePath: string) => ({
         message: `Script import path does not match casing of actual file path '${correctFilePath}'.`,
-        code: 1012,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1012,
+        severity: DiagnosticSeverity.Warning,
+        code: 'import-case-mismatch'
     }),
     fileNotReferencedByAnyOtherFile: () => ({
         message: `This file is not referenced by any other file in the project.`,
-        code: 1013,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1013,
+        severity: DiagnosticSeverity.Warning,
+        code: 'file-not-referenced'
     }),
-    unknownDiagnosticCode: (theUnknownCode: number) => ({
+    unknownDiagnosticCode: (theUnknownCode: number | string) => ({
         message: `Unknown diagnostic code ${theUnknownCode}`,
-        code: 1014,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1014,
+        severity: DiagnosticSeverity.Warning,
+        code: 'unknown-diagnostic-code'
     }),
     scriptSrcCannotBeEmpty: () => ({
         message: `Script import cannot be empty or whitespace`,
-        code: 1015,
-        severity: DiagnosticSeverity.Error
-    }),
-    expectedIdentifierAfterKeyword: (keywordText: string) => ({
-        message: `Expected identifier after '${keywordText}' keyword`,
-        code: 1016,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1015,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-import-path'
     }),
+    expectedIdentifier: (preceedingTokenText?: string) => {
+        let message = `Expected identifier`;
+        if (preceedingTokenText) {
+            message += ` after '${preceedingTokenText}'`;
+        }
+        return {
+            message: message,
+            legacyCode: 1016,
+            severity: DiagnosticSeverity.Error,
+            code: 'expected-identifier'
+        };
+    },
     missingCallableKeyword: () => ({
-        message: `Expected 'function' or 'sub' to preceed identifier`,
-        code: 1017,
-        severity: DiagnosticSeverity.Error
+        message: `Expected 'function' or 'sub' to precede identifier`,
+        legacyCode: 1017,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-leading-keyword'
     }),
-    expectedValidTypeToFollowAsKeyword: () => ({
+    __unused12: () => ({
         message: `Expected valid type to follow 'as' keyword`,
-        code: 1018,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1018,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-valid-type'
     }),
     bsFeatureNotSupportedInBrsFiles: (featureName) => ({
         message: `BrighterScript feature '${featureName}' is not supported in standard BrightScript files`,
-        code: 1019,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1019,
+        severity: DiagnosticSeverity.Error,
+        code: 'bs-feature-not-supported'
     }),
-    brsConfigJsonIsDeprecated: () => ({
+    __ununsed12: () => ({
         message: `'brsconfig.json' is deprecated. Please rename to 'bsconfig.json'`,
-        code: 1020,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1020,
+        severity: DiagnosticSeverity.Warning,
+        code: 'brsconfig-deprecated'
     }),
     bsConfigJsonHasSyntaxErrors: (message: string) => ({
         message: `Encountered syntax errors in bsconfig.json: ${message}`,
-        code: 1021,
-        severity: DiagnosticSeverity.Error
-    }),
-    itemIsDeprecated: () => ({
-        message: `Item is deprecated`,
-        code: 1022,
-        severity: DiagnosticSeverity.Hint
+        legacyCode: 1021,
+        severity: DiagnosticSeverity.Error,
+        code: 'bsconfig-syntax-errors'
     }),
+    itemIsDeprecated: (itemName?: string, deprecatedDescription?: string) => {
+        itemName ??= 'Item';
+        return {
+            message: `${itemName} has been deprecated${deprecatedDescription ? ': ' + deprecatedDescription : ''} `,
+            legacyCode: 1022,
+            severity: DiagnosticSeverity.Hint,
+            code: 'item-deprecated'
+        };
+    },
     cannotUseOverrideKeywordOnConstructorFunction: () => ({
         message: 'Override keyword is not allowed on class constructor method',
-        code: 1023,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1023,
+        severity: DiagnosticSeverity.Error,
+        code: 'override-keyword-on-constructor'
     }),
-    statementMustBeDeclaredAtTopOfFile: (statementKeyword: string) => ({
-        message: `'${statementKeyword}' statement must be declared at the top of the file`,
-        code: 1024,
-        severity: DiagnosticSeverity.Error
+    unexpectedStatementLocation: (statementKeyword: string, locationText: string) => ({
+        message: `'${statementKeyword}' statement must be declared ${locationText}`,
+        legacyCode: 1024,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-statement-location'
     }),
-    methodDoesNotExistOnType: (methodName: string, className: string) => ({
+    __unused8: (methodName: string, className: string) => ({
         message: `Method '${methodName}' does not exist on type '${className}'`,
-        code: 1025,
+        legacyCode: 1025,
         severity: DiagnosticSeverity.Error
     }),
     duplicateIdentifier: (memberName: string) => ({
         message: `Duplicate identifier '${memberName}'`,
-        code: 1026,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1026,
+        severity: DiagnosticSeverity.Error,
+        code: 'duplicate-identifier'
     }),
     missingOverrideKeyword: (ancestorClassName: string) => ({
         message: `Method has no override keyword but is declared in ancestor class '${ancestorClassName}'`,
-        code: 1027,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1027,
+        severity: DiagnosticSeverity.Error,
+        code: 'missing-override-keyword'
     }),
     nameCollision: (thisThingKind: string, thatThingKind: string, thatThingName: string) => ({
         message: `${thisThingKind} has same name as ${thatThingKind} '${thatThingName}'`,
-        code: 1028,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1028,
+        severity: DiagnosticSeverity.Error,
+        code: 'name-collision'
     }),
-    classCouldNotBeFound: (className: string, scopeName: string) => ({
+    __unused9: (className: string, scopeName: string) => ({
         message: `Class '${className}' could not be found when this file is included in scope '${scopeName}'`,
-        code: 1029,
+        legacyCode: 1029,
         severity: DiagnosticSeverity.Error,
         data: {
             className: className
         }
     }),
-    expectedClassFieldIdentifier: () => ({
+    __unused27: () => ({
         message: `Expected identifier in class body`,
-        code: 1030,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1030,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-identifier-in-body'
     }),
     expressionIsNotConstructable: (expressionType: string) => ({
         message: `Cannot use the 'new' keyword here because '${expressionType}' is not a constructable type`,
-        code: 1031,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1031,
+        severity: DiagnosticSeverity.Error,
+        code: 'not-constructable'
     }),
     expectedKeyword: (kind: TokenKind) => ({
         message: `Expected '${kind}' keyword`,
-        code: 1032,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1032,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-keyword'
     }),
-    expectedLeftParenAfterCallable: (callableType: string) => ({
+    __unused28: (callableType: string) => ({
         message: `Expected '(' after ${callableType}`,
-        code: 1033,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1033,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-left-paren-after-callable'
     }),
-    expectedNameAfterCallableKeyword: (callableType: string) => ({
+    __unused29: (callableType: string) => ({
         message: `Expected ${callableType} name after '${callableType}' keyword`,
-        code: 1034,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1034,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-name-after-callable'
     }),
-    expectedLeftParenAfterCallableName: (callableType: string) => ({
+    __unused30: (callableType: string) => ({
         message: `Expected '(' after ${callableType} name`,
-        code: 1035,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1035,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-left-paren-after-callable-name'
     }),
     tooManyCallableParameters: (actual: number, max: number) => ({
         message: `Cannot have more than ${max} parameters but found ${actual})`,
-        code: 1036,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1036,
+        severity: DiagnosticSeverity.Error,
+        code: 'exceeds-max-parameter-count'
     }),
     __unused: (typeText: string) => ({
         message: `Function return type '${typeText}' is invalid`,
-        code: 1037,
+        legacyCode: 1037,
         severity: DiagnosticSeverity.Error
     }),
     requiredParameterMayNotFollowOptionalParameter: (parameterName: string) => ({
         message: `Required parameter '${parameterName}' must be declared before any optional parameters`,
-        code: 1038,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1038,
+        severity: DiagnosticSeverity.Error,
+        code: 'required-parameter-before-optional'
     }),
     expectedNewlineOrColon: () => ({
         message: `Expected newline or ':' at the end of a statement`,
-        code: 1039,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1039,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-statement-separator'
     }),
-    functionNameCannotEndWithTypeDesignator: (callableType: string, name: string, designator: string) => ({
-        message: `${callableType} name '${name}' cannot end with type designator '${designator}'`,
-        code: 1040,
-        severity: DiagnosticSeverity.Error
+    invalidIdentifier: (name: string, character: string) => ({
+        message: `Identifier '${name}' may not contain the character '${character}'`,
+        legacyCode: 1040,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-identifier'
     }),
-    callableBlockMissingEndKeyword: (callableType: string) => ({
+    __unused31: (callableType: string) => ({
         message: `Expected 'end ${callableType}' to terminate ${callableType} block`,
-        code: 1041,
-        severity: DiagnosticSeverity.Error
-    }),
-    mismatchedEndCallableKeyword: (expectedCallableType: string, actualCallableType: string) => ({
-        message: `Expected 'end ${expectedCallableType?.replace(/^end\s*/, '')}' to terminate ${expectedCallableType} block but found 'end ${actualCallableType?.replace(/^end\s*/, '')}' instead.`,
-        code: 1042,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1041,
+        severity: DiagnosticSeverity.Error,
+        code: 'closing-keyword-mismatch'
     }),
+    closingKeywordMismatch: (expectedCallableType: string, actualCallableType: string) => {
+        let message = `Expected 'end ${expectedCallableType?.replace(/^end\s*/, '')}' to terminate ${expectedCallableType} block`;
+        if (actualCallableType) {
+            message += ` but found 'end ${actualCallableType?.replace(/^end\s*/, '')}' instead.`;
+        }
+        return {
+            message: message,
+            legacyCode: 1042,
+            severity: DiagnosticSeverity.Error,
+            code: 'closing-keyword'
+        };
+    },
     expectedParameterNameButFound: (text: string) => ({
         message: `Expected parameter name, but found '${text ?? ''}'`,
-        code: 1043,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1043,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-parameter-name'
     }),
     __unused2: (parameterName: string, typeText: string) => ({
         message: `Function parameter '${parameterName}' is of invalid type '${typeText}'`,
-        code: 1044,
+        legacyCode: 1044,
         severity: DiagnosticSeverity.Error
     }),
     cannotUseReservedWordAsIdentifier: (name: string) => ({
         message: `Cannot use reserved word '${name}' as an identifier`,
-        code: 1045,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1045,
+        severity: DiagnosticSeverity.Error,
+        code: 'cannot-use-reserved-word'
     }),
-    expectedOperatorAfterIdentifier: (operators: TokenKind[], name: string) => {
+    expectedOperator: (operators: TokenKind[], name: string) => {
         operators = Array.isArray(operators) ? operators : [];
+        let message = `Expected operator ('${operators.join(`', '`)}')`;
+        if (name) {
+            message += ` after idenfifier '${name}'`;
+        }
         return {
-            message: `Expected operator ('${operators.join(`', '`)}') after idenfifier '${name}'`,
-            code: 1046,
-            severity: DiagnosticSeverity.Error
+            message: message,
+            legacyCode: 1046,
+            severity: DiagnosticSeverity.Error,
+            code: 'expected-operator'
         };
     },
     expectedInlineIfStatement: () => ({
         message: `If/else statement within an inline if should be also inline`,
-        code: 1047,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1047,
+        severity: DiagnosticSeverity.Error,
+        code: 'malformed-inline-if'
     }),
     expectedFinalNewline: () => ({
         message: `Expected newline at the end of an inline if statement`,
-        code: 1048,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1048,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-final-newline'
     }),
     couldNotFindMatchingEndKeyword: (keyword: string) => ({
         message: `Could not find matching 'end ${keyword}'`,
-        code: 1049,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1049,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-end-keyword'
     }),
     expectedCatchBlockInTryCatch: () => ({
         message: `Expected 'catch' block in 'try' statement`,
-        code: 1050,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1050,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-catch'
     }),
-    expectedEndForOrNextToTerminateForLoop: () => ({
-        message: `Expected 'end for' or 'next' to terminate 'for' loop`,
-        code: 1051,
-        severity: DiagnosticSeverity.Error
+    expectedEndForOrNextToTerminateForLoop: (forLoopNameText: string = TokenKind.For) => ({
+        message: `Expected 'end for' or 'next' to terminate '${forLoopNameText}' loop`,
+        legacyCode: 1051,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-loop-terminator'
     }),
-    expectedInAfterForEach: (name: string) => ({
+    __unused32: (name: string) => ({
         message: `Expected 'in' after 'for each ${name}'`,
-        code: 1052,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1052,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-in-for-each'
     }),
     expectedExpressionAfterForEachIn: () => ({
         message: `Expected expression after 'in' keyword from 'for each' statement`,
-        code: 1053,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1053,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-loop-expression'
     }),
     unexpectedColonBeforeIfStatement: () => ({
         message: `Colon before 'if' statement is not allowed`,
-        code: 1054,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1054,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-leading-colon'
     }),
     expectedStringLiteralAfterKeyword: (keyword: string) => ({
-        message: `Missing string literal after '${keyword}' keyword`,
-        code: 1055,
-        severity: DiagnosticSeverity.Error
+        message: `Expected string literal after '${keyword}' keyword`,
+        legacyCode: 1055,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-string-literal'
     }),
     keywordMustBeDeclaredAtRootLevel: (keyword: string) => ({
         message: `${keyword} must be declared at the root level`,
-        code: 1056,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1056,
+        severity: DiagnosticSeverity.Error,
+        code: 'keyword-must-be-root-level'
     }),
     __unused5: () => ({
         message: `'library' statement must be declared at the top of the file`,
-        code: 1057,
+        legacyCode: 1057,
         severity: DiagnosticSeverity.Error
     }),
-    expectedEndIfElseIfOrElseToTerminateThenBlock: () => ({
+    expectedTerminator: (expectedTerminators: string[] | string, statementType: string, blockDescriptor: 'block' | 'statement' = 'statement') => ({
+        message: `Expected ${getPossibilitiesString(expectedTerminators)} to terminate '${statementType}' ${blockDescriptor}`,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-terminator'
+    }),
+    __unused14: () => ({
         message: `Expected 'end if', 'else if', or 'else' to terminate 'then' block`,
-        code: 1058,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1058,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-terminator-on-then'
     }),
-    expectedEndTryToTerminateTryCatch: () => ({
+    __unused15: () => ({
         message: `Expected 'end try' to terminate 'try-catch' statement`,
-        code: 1059,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1059,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-terminator-on-try-catch'
     }),
-    expectedEndIfToCloseIfStatement: (startingPosition: Position) => ({
-        message: `Expected 'end if' to close 'if' statement started at ${startingPosition?.line + 1}:${startingPosition?.character + 1}`,
-        code: 1060,
-        severity: DiagnosticSeverity.Error
+    __unused16: (startingPosition: Position) => ({
+        message: `Expected 'end if' to close 'if' statement started at ${startingPosition?.line + 1}:${startingPosition?.character + 1} `,
+        legacyCode: 1060,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-terminator-on-if'
     }),
-    expectedStatementToFollowConditionalCondition: (conditionType: string) => ({
+    expectedStatement: (conditionType?: string, extraDetail?: string) => {
+        let message = 'Expected statement';
+        if (conditionType) {
+            message += ` to follow '${conditionType?.toLowerCase()}'`;
+        }
+        if (extraDetail) {
+            message += ` ${extraDetail}`;
+        }
+        return {
+            message: message,
+            severity: DiagnosticSeverity.Error,
+            code: 'expected-statement'
+        };
+    },
+    __unused18: (conditionType: string) => ({
         message: `Expected a statement to follow '${conditionType?.toLowerCase()} ...condition... then'`,
-        code: 1061,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1061,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-statement-after-conditional'
     }),
-    expectedStatementToFollowElse: () => ({
+    __unused19: () => ({
         message: `Expected a statement to follow 'else'`,
-        code: 1062,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1062,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-statement-after-else'
     }),
-    consecutiveIncrementDecrementOperatorsAreNotAllowed: () => ({
-        message: `Consecutive increment/decrement operators are not allowed`,
-        code: 1063,
-        severity: DiagnosticSeverity.Error
+    unexpectedOperator: () => ({
+        message: `Unexpected operator`,
+        legacyCode: 1063,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-operator'
     }),
-    incrementDecrementOperatorsAreNotAllowedAsResultOfFunctionCall: () => ({
-        message: ``,
-        code: 1064,
-        severity: DiagnosticSeverity.Error
+    __unused13: () => ({
+        message: `Increment / decrement operators are not allowed on function calls`,
+        legacyCode: 1064,
+        severity: DiagnosticSeverity.Error,
+        code: 'increment-decrement-on-function-call'
     }),
     xmlUnexpectedTag: (tagName: string) => ({
         message: `Unexpected tag '${tagName}'`,
-        code: 1065,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1065,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-tag'
     }),
-    expectedStatementOrFunctionCallButReceivedExpression: () => ({
+    __unused20: () => ({
         message: `Expected statement or function call but instead found expression`,
-        code: 1066,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1066,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-statement-not-expression'
     }),
     xmlFunctionNotFound: (name: string) => ({
         message: `Cannot find function with name '${name}' in component scope`,
-        code: 1067,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1067,
+        severity: DiagnosticSeverity.Error,
+        code: 'function-not-found'
     }),
     xmlInvalidFieldType: (name: string) => ({
         message: `Invalid field type ${name}`,
-        code: 1068,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1068,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-field-type'
     }),
     xmlUnexpectedChildren: (tagName: string) => ({
         message: `Tag '${tagName}' should not have children`,
-        code: 1069,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1069,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-children'
     }),
     xmlTagMissingAttribute: (tagName: string, attrName: string) => ({
         message: `Tag '${tagName}' must have a '${attrName}' attribute`,
-        code: 1070,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1070,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-attribute'
     }),
     expectedLabelIdentifierAfterGotoKeyword: () => ({
         message: `Expected label identifier after 'goto' keyword`,
-        code: 1071,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1071,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-label'
     }),
-    expectedRightSquareBraceAfterArrayOrObjectIndex: () => ({
+    __unused26: () => ({
         message: `Expected ']' after array or object index`,
-        code: 1072,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1072,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-right-brace'
     }),
-    expectedPropertyNameAfterPeriod: () => ({
+    __unused21: () => ({
         message: `Expected property name after '.'`,
-        code: 1073,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1073,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-property-name'
     }),
     tooManyCallableArguments: (actual: number, max: number) => ({
-        message: `Cannot have more than ${max} arguments but found ${actual}`,
-        code: 1074,
-        severity: DiagnosticSeverity.Error
-    }),
-    expectedRightParenAfterFunctionCallArguments: () => ({
-        message: `Expected ')' after function call arguments`,
-        code: 1075,
-        severity: DiagnosticSeverity.Error
+        message: `Cannot have more than ${max} arguments but found ${actual} `,
+        legacyCode: 1074,
+        severity: DiagnosticSeverity.Error,
+        code: 'exceeds-max-argument-count'
     }),
-    unmatchedLeftParenAfterExpression: () => ({
+    /**
+     * @param unmatchedToken Should be one of '(', '[', or '{'
+     * @param afterDetail any additional message to describe what came before the unmatched token
+     */
+    unmatchedLeftToken: (unmatchedToken: string, afterDetail = '') => {
+        let matchingToken = '';
+        switch (unmatchedToken) {
+            case '(':
+                matchingToken = ')';
+                break;
+            case '[':
+                matchingToken = ']';
+                break;
+            case '{':
+                matchingToken = '}';
+                break;
+        }
+        let message = `Unmatched '${unmatchedToken}'`;
+        if (matchingToken) {
+            message += `: expected '${matchingToken}'`;
+        }
+        if (afterDetail) {
+            message += ` after ${afterDetail}`;
+        }
+        return {
+            message: message,
+            legacyCode: 1075,
+            severity: DiagnosticSeverity.Error,
+            code: 'unmatched-left-token'
+        };
+    },
+    __unused23: () => ({
         message: `Unmatched '(': expected ')' after expression`,
-        code: 1076,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1076,
+        severity: DiagnosticSeverity.Error,
+        code: 'unmatched-left-paren'
     }),
-    unmatchedLeftSquareBraceAfterArrayLiteral: () => ({
+    __unused24: () => ({
         message: `Unmatched '[': expected ']' after array literal`,
-        code: 1077,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1077,
+        severity: DiagnosticSeverity.Error,
+        code: 'unmatched-left-brace'
     }),
     unexpectedAAKey: () => ({
         message: `Expected identifier or string as associative array key`,
-        code: 1078,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1078,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-aa-key'
     }),
     expectedColonBetweenAAKeyAndvalue: () => ({
         message: `Expected ':' between associative array key and value`,
-        code: 1079,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1079,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-aa-separator'
     }),
-    unmatchedLeftCurlyAfterAALiteral: () => ({
+    __unused25: () => ({
         message: `Unmatched '{': expected '}' after associative array literal`,
-        code: 1080,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1080,
+        severity: DiagnosticSeverity.Error,
+        code: 'unmatched-left-curly'
     }),
     unexpectedToken: (text: string) => ({
         message: `Unexpected token '${text}'`,
-        code: 1081,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1081,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-token'
     }),
     /**
      * Used in the lexer anytime we encounter an unsupported character
      */
     unexpectedCharacter: (text: string) => ({
-        message: `Unexpected character '${text}' (char code ${text?.charCodeAt(0)})`,
-        code: 1082,
-        severity: DiagnosticSeverity.Error
+        message: `Unexpected character '${text}'(char code ${text?.charCodeAt(0)})`,
+        legacyCode: 1082,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-character'
     }),
-    unterminatedStringAtEndOfLine: () => ({
-        message: `Unterminated string at end of line`,
-        code: 1083,
-        severity: DiagnosticSeverity.Error
+    unterminatedString: () => ({
+        message: `Unterminated string literal`,
+        legacyCode: 1083,
+        severity: DiagnosticSeverity.Error,
+        code: 'unterminated-string'
     }),
-    unterminatedStringAtEndOfFile: () => ({
+    __unused33: () => ({
         message: `Unterminated string at end of file`,
-        code: 1084,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1084,
+        severity: DiagnosticSeverity.Error,
+        code: 'unterminated-string-at-end-of-file'
     }),
     fractionalHexLiteralsAreNotSupported: () => ({
         message: `Fractional hex literals are not supported`,
-        code: 1085,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1085,
+        severity: DiagnosticSeverity.Error,
+        code: 'fractional-hex-literal'
     }),
     unexpectedConditionalCompilationString: () => ({
-        message: `Unexpected conditional-compilation string`,
-        code: 1086,
-        severity: DiagnosticSeverity.Error
+        message: `Unknown conditional compile keyword`,
+        legacyCode: 1086,
+        severity: DiagnosticSeverity.Error,
+        code: 'unknown-conditional-compile-keyword'
     }),
     duplicateConstDeclaration: (name: string) => ({
         message: `Attempting to redeclare #const with name '${name}'`,
-        code: 1087,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1087,
+        severity: DiagnosticSeverity.Error,
+        code: 'duplicate-const-declaration'
     }),
-    constAliasDoesNotExist: (name: string) => ({
+    __unused34: (name: string) => ({
         message: `Attempting to create #const alias of '${name}', but no such #const exists`,
-        code: 1088,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1088,
+        severity: DiagnosticSeverity.Error,
+        code: 'const-alias-does-not-exist'
     }),
     invalidHashConstValue: () => ({
         message: '#const declarations can only have values of `true`, `false`, or other #const names',
-        code: 1089,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1089,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-hash-const-value'
     }),
-    referencedConstDoesNotExist: () => ({
+    hashConstDoesNotExist: () => ({
         message: `Referenced #const does not exist`,
-        code: 1090,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1090,
+        severity: DiagnosticSeverity.Error,
+        code: 'hash-const-does-not-exist'
     }),
     invalidHashIfValue: () => ({
         message: `#if conditionals can only be 'true', 'false', or other #const names`,
-        code: 1091,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1091,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-hash-if-value'
     }),
+    /**
+     * Treat #error directives like diagnostics, because the presence of `#error` even inside a runtime logical block that evaluates to false will 100% cause a compile error.
+     */
     hashError: (message: string) => ({
-        message: `#error ${message}`,
-        code: 1092,
-        severity: DiagnosticSeverity.Error
+        message: `#error ${message} `,
+        legacyCode: 1092,
+        severity: DiagnosticSeverity.Error,
+        code: 'hash-error'
     }),
-    expectedEqualAfterConstName: () => ({
+    __unused43: () => ({
         message: `Expected '=' after #const`,
-        code: 1093,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1093,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-equal-after-hash-const'
     }),
-    expectedHashEndIfToCloseHashIf: (startingLine: number) => ({
-        message: `Expected '#end if' to close '#if' conditional compilation statement starting on line ${startingLine}`,
-        code: 1094,
-        severity: DiagnosticSeverity.Error
+    __unused17: (startingLine: number) => ({
+        message: `Expected '#end if' to close '#if' conditional compilation statement starting on line ${startingLine} `,
+        legacyCode: 1094,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-terminator-on-hash-if'
     }),
-    constNameCannotBeReservedWord: () => ({
+    __unused35: () => ({
         message: `#const name cannot be a reserved word`,
-        code: 1095,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1095,
+        severity: DiagnosticSeverity.Error,
+        code: 'const-reservered-word'
     }),
-    expectedIdentifier: () => ({
+    __unused22: () => ({
         message: `Expected identifier`,
-        code: 1096,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1096,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-identifier'
     }),
     expectedAttributeNameAfterAtSymbol: () => ({
         message: `Expected xml attribute name after '@'`,
-        code: 1097,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1097,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-attribute-name'
     }),
     childFieldTypeNotAssignableToBaseProperty: (childTypeName: string, baseTypeName: string, fieldName: string, childFieldType: string, parentFieldType: string) => ({
-        message: `Field '${fieldName}' in class '${childTypeName}' is not assignable to the same field in base class '${baseTypeName}'. Type '${childFieldType}' is not assignable to type '${parentFieldType}'.`,
-        code: 1098,
-        severity: DiagnosticSeverity.Error
+        message: `Field '${fieldName}' in class '${childTypeName}' is not assignable to the same field in base class '${baseTypeName}'.Type '${childFieldType}' is not assignable to type '${parentFieldType}'.`,
+        legacyCode: 1098,
+        severity: DiagnosticSeverity.Error,
+        code: 'field-inheritance-mismatch'
     }),
-    classChildMemberDifferentMemberTypeThanAncestor: (memberType: string, parentMemberType: string, parentClassName: string) => ({
+    __unused36: (memberType: string, parentMemberType: string, parentClassName: string) => ({
         message: `Class member is a ${memberType} here but a ${parentMemberType} in ancestor class '${parentClassName}'`,
-        code: 1099,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1099,
+        severity: DiagnosticSeverity.Error,
+        code: 'child-field-type-different'
     }),
     classConstructorMissingSuperCall: () => ({
         message: `Missing "super()" call in class constructor method.`,
-        code: 1100,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1100,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-super-call'
     }),
     classConstructorIllegalUseOfMBeforeSuperCall: () => ({
         message: `Illegal use of "m" before calling "super()"`,
-        code: 1101,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1101,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-super-before-statement'
     }),
     classFieldCannotBeOverridden: () => ({
         message: `Class field cannot be overridden`,
-        code: 1102,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1102,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-field-override'
     }),
     unusedAnnotation: () => ({
         message: `This annotation is not attached to any statement`,
-        code: 1103,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1103,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-annotation'
     }),
     localVarShadowedByScopedFunction: () => ({
-        message: `Declaring a local variable with same name as scoped function can result in unexpected behavior`,
-        code: 1104,
-        severity: DiagnosticSeverity.Error
+        message: `Declaring a local variable with same name as scoped function or class can result in unexpected behavior`,
+        legacyCode: 1104,
+        severity: DiagnosticSeverity.Error,
+        code: 'var-shadows-function'
     }),
     scopeFunctionShadowedByBuiltInFunction: () => ({
-        message: `Scope function will not be accessible because it has the same name as a built-in function`,
-        code: 1105,
-        severity: DiagnosticSeverity.Error
+        message: `Scope function will not be accessible because it has the same name as a built -in function`,
+        legacyCode: 1105,
+        severity: DiagnosticSeverity.Error,
+        code: 'native-function-collision'
     }),
-    localVarSameNameAsClass: (className: string) => ({
+    __unused38: (className: string) => ({
         message: `Local variable has same name as class '${className}'`,
-        code: 1106,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1106,
+        severity: DiagnosticSeverity.Error,
+        code: 'local-var-same-name-as-class'
     }),
     unnecessaryCodebehindScriptImport: () => ({
         message: `This import is unnecessary because compiler option 'autoImportComponentScript' is enabled`,
-        code: 1107,
-        severity: DiagnosticSeverity.Warning
+        legacyCode: 1107,
+        severity: DiagnosticSeverity.Warning,
+        code: 'unnecessary-import'
     }),
-    expectedOpenParenToFollowCallfuncIdentifier: () => ({
+    __unused37: () => ({
         message: `Expected '(' to follow callfunc identifier`,
-        code: 1108,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1108,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-left-paren-after-callfunc'
     }),
     expectedToken: (...tokenKinds: string[]) => ({
         message: `Expected token '${tokenKinds.join(`' or '`)}'`,
-        code: 1109,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1109,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-token'
     }),
-    __unused8: (paramName: string) => ({
+    __unused10: (paramName: string) => ({
         message: `Parameter '${paramName}' may not have the same name as namespace`,
-        code: 1110,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1110,
+        severity: DiagnosticSeverity.Error,
+        code: 'parameter-same-name-as-namespace'
     }),
-    __unused9: (variableName: string) => ({
+    __unused11: (variableName: string) => ({
         message: `Variable '${variableName}' may not have the same name as namespace`,
-        code: 1111,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1111,
+        severity: DiagnosticSeverity.Error,
+        code: 'variable-same-name-as-namespace'
     }),
-    unterminatedTemplateStringAtEndOfFile: () => ({
-        message: `Unterminated template string at end of file`,
-        code: 1113,
-        severity: DiagnosticSeverity.Error
+    unterminatedTemplateString: () => ({
+        message: `Unterminated template string`,
+        legacyCode: 1113,
+        severity: DiagnosticSeverity.Error,
+        code: 'unterminated-template-string'
     }),
     unterminatedTemplateExpression: () => ({
         message: `Unterminated template string expression. '\${' must be followed by expression, then '}'`,
-        code: 1114,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1114,
+        severity: DiagnosticSeverity.Error,
+        code: 'unterminated-template-string-expression'
     }),
     duplicateComponentName: (componentName: string) => ({
         message: `There are multiple components with the name '${componentName}'`,
-        code: 1115,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1115,
+        severity: DiagnosticSeverity.Error,
+        code: 'duplicate-component-name'
     }),
     __unused6: (className: string) => ({
         message: `Function has same name as class '${className}'`,
-        code: 1116,
+        legacyCode: 1116,
         severity: DiagnosticSeverity.Error
     }),
     expectedExceptionVarToFollowCatch: () => ({
         message: `Expected exception variable after 'catch' keyword`,
-        code: 1117,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1117,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-exception-variable'
     }),
     missingExceptionExpressionAfterThrowKeyword: () => ({
         message: `Missing exception expression after 'throw' keyword`,
-        code: 1118,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1118,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-throw-expression'
     }),
-    missingLeftSquareBracketAfterDimIdentifier: () => ({
+    __unused42: () => ({
         message: `Missing left square bracket after 'dim' identifier`,
-        code: 1119,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1119,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-left-brace-after-dim'
     }),
-    missingRightSquareBracketAfterDimIdentifier: () => ({
+    __unused39: () => ({
         message: `Missing right square bracket after 'dim' identifier`,
-        code: 1120,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1120,
+        severity: DiagnosticSeverity.Error,
+        code: 'missing-right-brace-after-dim'
     }),
     missingExpressionsInDimStatement: () => ({
         message: `Missing expression(s) in 'dim' statement`,
-        code: 1121,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1121,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-dim-expression'
     }),
     mismatchedOverriddenMemberVisibility: (childClassName: string, memberName: string, childAccessModifier: string, ancestorAccessModifier: string, ancestorClassName: string) => ({
         message: `Access modifier mismatch: '${memberName}' is ${childAccessModifier} in type '${childClassName}' but is ${ancestorAccessModifier} in base type '${ancestorClassName}'.`,
-        code: 1122,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1122,
+        severity: DiagnosticSeverity.Error,
+        code: 'access-modifier-mismatch'
     }),
     __unused3: (typeName: string) => ({
         message: `Cannot find type with name '${typeName}'`,
-        code: 1123,
+        legacyCode: 1123,
         severity: DiagnosticSeverity.Error
     }),
     enumValueMustBeType: (expectedType: string) => ({
         message: `Enum value must be type '${expectedType}'`,
-        code: 1124,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1124,
+        severity: DiagnosticSeverity.Error,
+        code: 'enum-type-mismatch'
     }),
     enumValueIsRequired: (expectedType: string) => ({
         message: `Value is required for ${expectedType} enum`,
-        code: 1125,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1125,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-enum-value'
     }),
-    unknownEnumValue: (name: string, enumName: string) => ({
+    __unused40: (name: string, enumName: string) => ({
         message: `Property '${name}' does not exist on enum '${enumName}'`,
-        code: 1126,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1126,
+        severity: DiagnosticSeverity.Error,
+        code: 'unknown-enum-value'
     }),
     __unused7: (scopeName: string, enumName: string) => ({
         message: `Scope '${scopeName}' already contains an enum with name '${enumName}'`,
-        code: 1127,
+        legacyCode: 1127,
         severity: DiagnosticSeverity.Error
     }),
     unknownRoSGNode: (nodeName: string) => ({
         message: `Unknown roSGNode '${nodeName}'`,
-        code: 1128,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1128,
+        severity: DiagnosticSeverity.Error,
+        code: 'unknown-rosgnode'
     }),
     unknownBrightScriptComponent: (componentName: string) => ({
         message: `Unknown BrightScript component '${componentName}'`,
-        code: 1129,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1129,
+        severity: DiagnosticSeverity.Error,
+        code: 'unknown-brightscript-component'
     }),
     mismatchCreateObjectArgumentCount: (componentName: string, allowedArgCounts: number[], actualCount: number) => {
         const argCountArray = (allowedArgCounts || [1]).sort().filter((value, index, self) => self.indexOf(value) === index);
         return {
             message: `For ${componentName}, expected ${argCountArray.map(c => c.toString()).join(' or ')} total arguments, but got ${actualCount}.`,
-            code: 1130,
-            severity: DiagnosticSeverity.Error
+            legacyCode: 1130,
+            severity: DiagnosticSeverity.Error,
+            code: 'incorrect-createobject-argument-count'
         };
     },
-    deprecatedBrightScriptComponent: (componentName: string, deprecatedDescription?: string) => ({
-        message: `${componentName} has been deprecated${deprecatedDescription ? ': ' + deprecatedDescription : ''}`,
-        code: 1131,
-        severity: DiagnosticSeverity.Error
+    __unused41: (componentName: string, deprecatedDescription?: string) => ({
+        message: `${componentName} has been deprecated${deprecatedDescription ? ': ' + deprecatedDescription : ''} `,
+        legacyCode: 1131,
+        severity: DiagnosticSeverity.Error,
+        code: 'deprecated-brightscript-component'
     }),
     circularReferenceDetected: (items: string[], scopeName: string) => ({
-        message: `Circular reference detected between ${Array.isArray(items) ? items.join(' -> ') : ''} in scope '${scopeName}'`,
-        code: 1132,
-        severity: DiagnosticSeverity.Error
+        message: `Circular inheritance detected between ${Array.isArray(items) ? items.join(' -> ') : ''} in scope '${scopeName}'`,
+        legacyCode: 1132,
+        severity: DiagnosticSeverity.Error,
+        code: 'circular-inheritance'
     }),
     unexpectedStatementOutsideFunction: () => ({
         message: `Unexpected statement found outside of function body`,
-        code: 1133,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1133,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-statement'
     }),
     detectedTooDeepFileSource: (numberOfParentDirectories: number) => ({
-        message: `Expected directory depth no larger than 7, but found ${numberOfParentDirectories}`,
-        code: 1134,
-        severity: DiagnosticSeverity.Error
+        message: `Expected directory depth no larger than 7, but found ${numberOfParentDirectories} `,
+        legacyCode: 1134,
+        severity: DiagnosticSeverity.Error,
+        code: 'directory-depth'
     }),
     illegalContinueStatement: () => ({
         message: `Continue statement must be contained within a loop statement`,
-        code: 1135,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1135,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-continue'
     }),
     keywordMustBeDeclaredAtNamespaceLevel: (keyword: string) => ({
         message: `${keyword} must be declared at the root level or within a namespace`,
-        code: 1136,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1136,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-declaration-location'
     }),
     itemCannotBeUsedAsVariable: (itemType: string) => ({
         message: `${itemType} cannot be used as a variable`,
-        code: 1137,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1137,
+        severity: DiagnosticSeverity.Error,
+        code: 'type-not-variable'
     }),
     callfuncHasToManyArgs: (numberOfArgs: number) => ({
-        message: `You can not have more than 5 arguments in a callFunc. ${numberOfArgs} found.`,
-        code: 1138,
-        severity: DiagnosticSeverity.Error
+        message: `You can not have more than 5 arguments in a callFunc.${numberOfArgs} found.`,
+        legacyCode: 1138,
+        severity: DiagnosticSeverity.Error,
+        code: 'exceeds-max-callfunc-arg-count'
     }),
     noOptionalChainingInLeftHandSideOfAssignment: () => ({
         message: `Optional chaining may not be used in the left-hand side of an assignment`,
-        code: 1139,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1139,
+        severity: DiagnosticSeverity.Error,
+        code: 'unexpected-optional-chain'
     }),
     /**
      *
@@ -738,90 +938,99 @@ export let DiagnosticMessages = {
      * @param typeDescriptor defaults to 'type' ... could also be 'namespace', etc.
      */
     cannotFindFunction: (name: string, fullName?: string, typeName?: string, typeDescriptor = 'type') => ({
-        message: `Cannot find function '${name}'${typeName ? ` for ${typeDescriptor} '${typeName}'` : ''}`,
-        code: 1140,
+        message: `Cannot find function '${name}'${typeName ? ` for ${typeDescriptor} '${typeName}'` : ''} `,
+        legacyCode: 1140,
         data: {
             name: name,
             fullName: fullName ?? name,
             typeName: typeName ? typeName : undefined
         },
-        severity: DiagnosticSeverity.Error
+        severity: DiagnosticSeverity.Error,
+        code: 'cannot-find-function'
     }),
     argumentTypeMismatch: (actualTypeString: string, expectedTypeString: string, data?: TypeCompatibilityData) => ({
-        message: `Argument of type '${actualTypeString}' is not compatible with parameter of type '${expectedTypeString}'${typeCompatibilityMessage(actualTypeString, expectedTypeString, data)}`,
+        message: `Argument of type '${actualTypeString}' is not compatible with parameter of type '${expectedTypeString}'${typeCompatibilityMessage(actualTypeString, expectedTypeString, data)} `,
         data: data,
-        code: 1141,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1141,
+        severity: DiagnosticSeverity.Error,
+        code: 'argument-type-mismatch'
     }),
     returnTypeMismatch: (actualTypeString: string, expectedTypeString: string, data?: TypeCompatibilityData) => ({
-        message: `Type '${actualTypeString}' is not compatible with declared return type '${expectedTypeString}'${typeCompatibilityMessage(actualTypeString, expectedTypeString, data)}'`,
+        message: `Type '${actualTypeString}' is not compatible with declared return type '${expectedTypeString}'${typeCompatibilityMessage(actualTypeString, expectedTypeString, data)} '`,
         data: data,
-        code: 1142,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1142,
+        severity: DiagnosticSeverity.Error,
+        code: 'return-type-mismatch'
     }),
     assignmentTypeMismatch: (actualTypeString: string, expectedTypeString: string, data?: TypeCompatibilityData) => ({
         message: `Type '${actualTypeString}' is not compatible with type '${expectedTypeString}'${typeCompatibilityMessage(actualTypeString, expectedTypeString, data)}`,
         data: data,
-        code: 1143,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1143,
+        severity: DiagnosticSeverity.Error,
+        code: 'assignment-type-mismatch'
     }),
     operatorTypeMismatch: (operatorString: string, firstType: string, secondType = '') => ({
         message: `Operator '${operatorString}' cannot be applied to type${secondType ? 's' : ''} '${firstType}'${secondType ? ` and '${secondType}'` : ''}`,
-        code: 1144,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1144,
+        severity: DiagnosticSeverity.Error,
+        code: 'operator-type-mismatch'
     }),
     incompatibleSymbolDefinition: (symbol: string, scopeName: string) => ({
         message: `'${symbol}' is incompatible across these scopes: ${scopeName}`,
-        code: 1145,
-        severity: DiagnosticSeverity.Error
-    }),
-    memberAccessibilityMismatch: (memberName: string, accessModifierFlag: SymbolTypeFlag, definingClassName: string) => ({
-        message: `Member '${memberName}' is ${accessModifierNameFromFlag(accessModifierFlag)}${accessModifierAdditionalInfo(accessModifierFlag, definingClassName)}`, // TODO: Add scopes where it was defined
-        code: 1146,
-        severity: DiagnosticSeverity.Error
-    }),
-    typecastStatementMustBeDeclaredAtStart: () => ({
-        message: `'typecast' statement must be declared at the top of the file or beginning of function or namespace`,
-        code: 1147,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1145,
+        severity: DiagnosticSeverity.Error,
+        code: 'incompatible-definition'
     }),
+    memberAccessibilityMismatch: (memberName: string, accessModifierFlag: SymbolTypeFlag, definingClassName: string) => {
+        let accessModName: string = TokenKind.Public;
+        // eslint-disable-next-line no-bitwise
+        if (accessModifierFlag & SymbolTypeFlag.private) {
+            accessModName = TokenKind.Private;
+            // eslint-disable-next-line no-bitwise
+        } else if (accessModifierFlag & SymbolTypeFlag.protected) {
+            accessModName = TokenKind.Protected;
+        }
+        accessModName = accessModName.toLowerCase();
+        let accessAdditionalInfo = '';
+
+        // eslint-disable-next-line no-bitwise
+        if (accessModifierFlag & SymbolTypeFlag.private) {
+            accessAdditionalInfo = ` and only accessible from within class '${definingClassName}'`;
+            // eslint-disable-next-line no-bitwise
+        } else if (accessModifierFlag & SymbolTypeFlag.protected) {
+            accessAdditionalInfo = ` and only accessible from within class '${definingClassName}' and its subclasses`;
+        }
+
+        return {
+            message: `Member '${memberName}' is ${accessModName}${accessAdditionalInfo}`, // TODO: Add scopes where it was defined
+            legacyCode: 1146,
+            severity: DiagnosticSeverity.Error,
+            code: 'member-access-violation'
+        };
+    },
     invalidTypecastStatementApplication: (foundApplication: string) => ({
         message: `'typecast' statement can only be applied to 'm', but was applied to '${foundApplication}'`,
-        code: 1148,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1148,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-typecast-target'
     }),
     itemCannotBeUsedAsType: (typeText: string) => ({
         message: `'${typeText}' cannot be used as a type`,
-        code: 1149,
-        severity: DiagnosticSeverity.Error
-    }),
-    expectedNewlineInConditionalCompile: () => ({
-        message: `Expected newline in conditional compilation statement`,
-        code: 1151,
-        severity: DiagnosticSeverity.Error
-    }),
-    expectedTerminatorOnConditionalCompileBlock: () => ({
-        message: `Expected '#end if', '#else if', or '#else' to terminate conditional compilation block`,
-        code: 1152,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1149,
+        severity: DiagnosticSeverity.Error,
+        code: 'invalid-type-reference'
     }),
     unsafeUnmatchedTerminatorInConditionalCompileBlock: (terminator: string) => ({
         message: `Unsafe unmatched terminator '${terminator}' in conditional compilation block`,
-        code: 1153,
-        severity: DiagnosticSeverity.Error
-    }),
-    cannotFindTypeInCommentDoc: (name: string) => ({
-        message: `Cannot find type '${name}' in doc comment`,
-        code: 1154,
-        data: {
-            name: name
-        },
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1150,
+        severity: DiagnosticSeverity.Error,
+        code: 'inconsistent-conditional-compile-nesting'
     }),
     expectedReturnStatement: () => ({
         message: `Expected return statement in function`,
-        code: 1155,
-        severity: DiagnosticSeverity.Error
+        legacyCode: 1151,
+        severity: DiagnosticSeverity.Error,
+        code: 'expected-return-statement'
     })
 };
 export const defaultMaximumTruncationLength = 160;
@@ -852,39 +1061,39 @@ export function typeCompatibilityMessage(actualTypeString: string, expectedTypeS
     return message;
 }
 
-function accessModifierNameFromFlag(accessModifierFlag: SymbolTypeFlag) {
-    let result = TokenKind.Public;
-    // eslint-disable-next-line no-bitwise
-    if (accessModifierFlag & SymbolTypeFlag.private) {
-        result = TokenKind.Private;
-        // eslint-disable-next-line no-bitwise
-    } else if (accessModifierFlag & SymbolTypeFlag.protected) {
-        result = TokenKind.Protected;
+function getPossibilitiesString(possibilities: string[] | string) {
+    if (!Array.isArray(possibilities)) {
+        return `'${possibilities}'`;
     }
-    return result.toLowerCase();
-}
+    if (possibilities.length === 1) {
+        return `'${possibilities}'`;
 
-function accessModifierAdditionalInfo(accessModifierFlag: SymbolTypeFlag, className: string) {
-    // eslint-disable-next-line no-bitwise
-    if (accessModifierFlag & SymbolTypeFlag.private) {
-        return ` and only accessible from within class '${className}'`;
-        // eslint-disable-next-line no-bitwise
-    } else if (accessModifierFlag & SymbolTypeFlag.protected) {
-        return ` and only accessible from within class '${className}' and its subclasses`;
     }
-    return TokenKind.Public;
+    let result = '';
+    for (let i = 0; i < possibilities.length; i++) {
+        result += `'${possibilities[i]}'`;
+        if (i < possibilities.length - 2) {
+            result += ', ';
+        } else if (i < possibilities.length - 1) {
+            result += ' or ';
+        }
+    }
+    return result;
 }
 
-export const DiagnosticCodeMap = {} as Record<keyof (typeof DiagnosticMessages), number>;
-export let diagnosticCodes = [] as number[];
+export const DiagnosticCodeMap = {} as Record<keyof (typeof DiagnosticMessages), string>;
+export const DiagnosticLegacyCodeMap = {} as Record<keyof (typeof DiagnosticMessages), number>;
+export let diagnosticCodes = [] as string[];
 for (let key in DiagnosticMessages) {
     diagnosticCodes.push(DiagnosticMessages[key]().code);
+    diagnosticCodes.push(DiagnosticMessages[key]().legacyCode);
     DiagnosticCodeMap[key] = DiagnosticMessages[key]().code;
+    DiagnosticLegacyCodeMap[key] = DiagnosticMessages[key]().legacyCode;
 }
 
 export interface DiagnosticInfo {
     message: string;
-    code: number;
+    legacyCode: number;
     severity: DiagnosticSeverity;
 }
 
diff --git a/src/DiagnosticSeverityAdjuster.ts b/src/DiagnosticSeverityAdjuster.ts
index b651f30b..d5ef5afb 100644
--- a/src/DiagnosticSeverityAdjuster.ts
+++ b/src/DiagnosticSeverityAdjuster.ts
@@ -11,6 +11,10 @@ export class DiagnosticSeverityAdjuster {
             if (map.has(code)) {
                 diagnostic.severity = map.get(code);
             }
+            const legacyCode = String(diagnostic.legacyCode);
+            if (legacyCode && map.has(legacyCode)) {
+                diagnostic.severity = map.get(legacyCode);
+            }
         });
     }
 
diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts
index 37157612..0500aebb 100644
--- a/src/LanguageServer.ts
+++ b/src/LanguageServer.ts
@@ -37,7 +37,6 @@ import { URI } from 'vscode-uri';
 import { TextDocument } from 'vscode-languageserver-textdocument';
 import type { BsConfig } from './BsConfig';
 import { Deferred } from './deferred';
-import { DiagnosticMessages } from './DiagnosticMessages';
 import { ProgramBuilder } from './ProgramBuilder';
 import { standardizePath as s, util } from './util';
 import { Throttler } from './Throttler';
@@ -595,14 +594,6 @@ export class LanguageServer {
             newProject.isFirstRunComplete = true;
             newProject.isFirstRunSuccessful = false;
         }
-        //if we found a deprecated brsconfig.json, add a diagnostic warning the user
-        if (configFilePath && path.basename(configFilePath) === 'brsconfig.json') {
-            builder.addDiagnostic(configFilePath, {
-                ...DiagnosticMessages.brsConfigJsonIsDeprecated(),
-                location: util.createLocationFromRange(util.pathToUri(configFilePath), util.createRange(0, 0, 0, 0))
-            });
-            return this.sendDiagnostics();
-        }
     }
 
     private async createStandaloneFileProject(srcPath: string) {
diff --git a/src/Program.spec.ts b/src/Program.spec.ts
index 3519a5ca..50ef5cf3 100644
--- a/src/Program.spec.ts
+++ b/src/Program.spec.ts
@@ -1138,10 +1138,13 @@ describe('Program', () => {
             `);
             //the file should be included in the program
             expect(program.getFile('components/a/b/c/main.brs')).to.exist;
-            let diagnostics = program.getDiagnostics();
-            expectHasDiagnostics(diagnostics);
-            let parseError = diagnostics.filter(x => x.message === 'Unterminated string at end of line')[0];
-            expect(parseError).to.exist;
+            expectDiagnostics(program, [
+                DiagnosticMessages.expectedStatement(),
+                {
+                    ...DiagnosticMessages.unterminatedString(),
+                    location: util.createLocation(2, 20, 2, 49, s`${rootDir}/components/a/b/c/main.brs`)
+                }
+            ]);
         });
 
         it('it excludes specified error codes', () => {
@@ -2702,7 +2705,7 @@ describe('Program', () => {
 
             //the buffers should be identical
             expect(
-                data.compare(result)
+                data.compare(result as any)
             ).to.equal(0);
         });
 
diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts
index 311a097f..4093e116 100644
--- a/src/Scope.spec.ts
+++ b/src/Scope.spec.ts
@@ -1011,11 +1011,14 @@ describe('Scope', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.nameCollision('Function', 'Global Function', 'Str').message,
                     {
+                        message: DiagnosticMessages.nameCollision('Function', 'Global Function', 'Str').message,
+                        location: { range: Range.create(4, 29, 4, 32) }
+                    }, {
                         message: DiagnosticMessages.scopeFunctionShadowedByBuiltInFunction().message,
                         location: { range: Range.create(4, 29, 4, 32) }
-                    }]);
+                    }
+                ]);
             });
         });
 
@@ -4278,7 +4281,7 @@ describe('Scope', () => {
                 end sub
             `);
             program.validate();
-            expectDiagnosticsIncludes(program, DiagnosticMessages.localVarSameNameAsClass('Person').message);
+            expectDiagnosticsIncludes(program, DiagnosticMessages.localVarShadowedByScopedFunction().message);
         });
 
         it('disallows reusing a class name as "for each" variable in a method', () => {
@@ -4295,7 +4298,7 @@ describe('Scope', () => {
                 end class
             `);
             program.validate();
-            expectDiagnosticsIncludes(program, DiagnosticMessages.localVarSameNameAsClass('Person').message);
+            expectDiagnosticsIncludes(program, DiagnosticMessages.localVarShadowedByScopedFunction().message);
         });
 
         it('allows reusing a namespaced class name as "for each" variable in a method', () => {
diff --git a/src/XmlScope.spec.ts b/src/XmlScope.spec.ts
index 29421ae8..1517494f 100644
--- a/src/XmlScope.spec.ts
+++ b/src/XmlScope.spec.ts
@@ -129,7 +129,7 @@ describe('XmlScope', () => {
                 ...DiagnosticMessages.xmlTagMissingAttribute('function', 'name'),
                 location: { range: Range.create(7, 9, 7, 17) }
             }, { // syntax error expecting '=' but found '/>'
-                code: DiagnosticMessages.xmlGenericParseError('').code
+                code: DiagnosticMessages.syntaxError('').code
             }, { // onChange function
                 ...DiagnosticMessages.xmlFunctionNotFound('func4'),
                 location: { range: Range.create(8, 51, 8, 56) }
@@ -178,7 +178,7 @@ describe('XmlScope', () => {
                 ...DiagnosticMessages.xmlTagMissingAttribute('field', 'type'),
                 location: { range: Range.create(9, 9, 9, 14) }
             }, { // syntax error expecting '=' but found '/>'
-                code: DiagnosticMessages.xmlGenericParseError('').code
+                code: DiagnosticMessages.syntaxError('').code
             }]);
         });
     });
diff --git a/src/bscPlugin/codeActions/CodeActionsProcessor.ts b/src/bscPlugin/codeActions/CodeActionsProcessor.ts
index ed330a6c..23157d88 100644
--- a/src/bscPlugin/codeActions/CodeActionsProcessor.ts
+++ b/src/bscPlugin/codeActions/CodeActionsProcessor.ts
@@ -21,8 +21,6 @@ export class CodeActionsProcessor {
         for (const diagnostic of this.event.diagnostics) {
             if (diagnostic.code === DiagnosticCodeMap.cannotFindName || diagnostic.code === DiagnosticCodeMap.cannotFindFunction) {
                 this.suggestCannotFindName(diagnostic as any);
-            } else if (diagnostic.code === DiagnosticCodeMap.classCouldNotBeFound) {
-                this.suggestClassImports(diagnostic as any);
             } else if (diagnostic.code === DiagnosticCodeMap.xmlComponentMissingExtendsAttribute) {
                 this.addMissingExtends(diagnostic as any);
             }
@@ -86,20 +84,6 @@ export class CodeActionsProcessor {
         );
     }
 
-    private suggestClassImports(diagnostic: DiagnosticMessageType<'classCouldNotBeFound'>) {
-        //skip if not a BrighterScript file
-        const file = this.event.program.getFile(diagnostic.location?.uri);
-        if (!file || (file as BrsFile).parseMode !== ParseMode.BrighterScript) {
-            return;
-        }
-        const lowerClassName = diagnostic.data.className.toLowerCase();
-        this.suggestImports(
-            diagnostic,
-            lowerClassName,
-            this.event.program.findFilesForClass(lowerClassName)
-        );
-    }
-
     private addMissingExtends(diagnostic: DiagnosticMessageType<'xmlComponentMissingExtendsAttribute'>) {
         const srcPath = this.event.file.srcPath;
         const { componentElement } = (this.event.file as XmlFile).parser.ast;
diff --git a/src/bscPlugin/validation/BrsFileValidator.spec.ts b/src/bscPlugin/validation/BrsFileValidator.spec.ts
index 802f7464..c17f0136 100644
--- a/src/bscPlugin/validation/BrsFileValidator.spec.ts
+++ b/src/bscPlugin/validation/BrsFileValidator.spec.ts
@@ -354,8 +354,8 @@ describe('BrsFileValidator', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.typecastStatementMustBeDeclaredAtStart().message,
-                DiagnosticMessages.typecastStatementMustBeDeclaredAtStart().message
+                DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace').message,
+                DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace').message
             ]);
         });
 
@@ -393,7 +393,7 @@ describe('BrsFileValidator', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.typecastStatementMustBeDeclaredAtStart().message
+                DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace').message
             ]);
         });
 
@@ -425,7 +425,7 @@ describe('BrsFileValidator', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.typecastStatementMustBeDeclaredAtStart().message
+                DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace').message
             ]);
         });
 
@@ -636,8 +636,8 @@ describe('BrsFileValidator', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('alias').message,
-                DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('alias').message
+                DiagnosticMessages.unexpectedStatementLocation('alias', 'at the top of the file').message,
+                DiagnosticMessages.unexpectedStatementLocation('alias', 'at the top of the file').message
             ]);
         });
 
@@ -995,7 +995,7 @@ describe('BrsFileValidator', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.cannotFindTypeInCommentDoc('TypeNotThere').message
+                    DiagnosticMessages.cannotFindName('TypeNotThere').message
                 ]);
                 const data = {} as ExtraSymbolData;
                 expectTypeToBe(
@@ -1065,7 +1065,7 @@ describe('BrsFileValidator', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.cannotFindTypeInCommentDoc('TypeNotThere').message
+                    DiagnosticMessages.cannotFindName('TypeNotThere').message
                 ]);
                 const data = {} as ExtraSymbolData;
                 const funcStmt = file.ast.findChild(isFunctionStatement);
@@ -1172,7 +1172,7 @@ describe('BrsFileValidator', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.cannotFindTypeInCommentDoc('unknown').message
+                    DiagnosticMessages.cannotFindName('unknown').message
                 ]);
                 const data = {} as ExtraSymbolData;
                 const funcStmt = file.ast.findChild(isFunctionStatement);
diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts
index 57059e2c..e4c3ff0f 100644
--- a/src/bscPlugin/validation/BrsFileValidator.ts
+++ b/src/bscPlugin/validation/BrsFileValidator.ts
@@ -450,7 +450,7 @@ export class BrsFileValidator {
         const isBool = ccConst.kind === TokenKind.True || ccConst.kind === TokenKind.False;
         if (!isBool && !this.event.file.ast.bsConsts.has(ccConst.text.toLowerCase())) {
             this.event.program.diagnostics.register({
-                ...DiagnosticMessages.referencedConstDoesNotExist(),
+                ...DiagnosticMessages.hashConstDoesNotExist(),
                 location: ccConst.location
             });
             return false;
@@ -523,17 +523,17 @@ export class BrsFileValidator {
             if (!topOfFileStatements.includes(result)) {
                 if (isLibraryStatement(result)) {
                     this.event.program.diagnostics.register({
-                        ...DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('library'),
+                        ...DiagnosticMessages.unexpectedStatementLocation('library', 'at the top of the file'),
                         location: result.location
                     });
                 } else if (isImportStatement(result)) {
                     this.event.program.diagnostics.register({
-                        ...DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('import'),
+                        ...DiagnosticMessages.unexpectedStatementLocation('import', 'at the top of the file'),
                         location: result.location
                     });
                 } else if (isAliasStatement(result)) {
                     this.event.program.diagnostics.register({
-                        ...DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('alias'),
+                        ...DiagnosticMessages.unexpectedStatementLocation('alias', 'at the top of the file'),
                         location: result.location
                     });
                 }
@@ -548,7 +548,7 @@ export class BrsFileValidator {
         for (let i = 1; i < topOfFileTypecastStatements.length; i++) {
             const typecastStmt = topOfFileTypecastStatements[i];
             this.event.program.diagnostics.register({
-                ...DiagnosticMessages.typecastStatementMustBeDeclaredAtStart(),
+                ...DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace'),
                 location: typecastStmt.location
             });
         }
@@ -579,7 +579,7 @@ export class BrsFileValidator {
 
             if (!isFirst || !isAllowedBlock) {
                 this.event.program.diagnostics.register({
-                    ...DiagnosticMessages.typecastStatementMustBeDeclaredAtStart(),
+                    ...DiagnosticMessages.unexpectedStatementLocation('typecast', 'at the top of the file or beginning of function or namespace'),
                     location: result.location
                 });
             }
diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts
index 64c9a339..4b9577c9 100644
--- a/src/bscPlugin/validation/ScopeValidator.spec.ts
+++ b/src/bscPlugin/validation/ScopeValidator.spec.ts
@@ -3330,7 +3330,7 @@ describe('ScopeValidator', () => {
                 `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.cannotFindTypeInCommentDoc('TypeNotThere').message
+                DiagnosticMessages.cannotFindName('TypeNotThere').message
             ]);
         });
 
@@ -3343,7 +3343,7 @@ describe('ScopeValidator', () => {
                 `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.cannotFindTypeInCommentDoc('TypeNotThere').message
+                DiagnosticMessages.cannotFindName('TypeNotThere').message
             ]);
         });
 
@@ -3356,7 +3356,7 @@ describe('ScopeValidator', () => {
                 `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.cannotFindTypeInCommentDoc('TypeNotThere').message
+                DiagnosticMessages.cannotFindName('TypeNotThere').message
             ]);
         });
 
diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts
index 59940a74..755adb9a 100644
--- a/src/bscPlugin/validation/ScopeValidator.ts
+++ b/src/bscPlugin/validation/ScopeValidator.ts
@@ -332,7 +332,7 @@ export class ScopeValidator {
             // Test for deprecation
             if (brightScriptComponent?.isDeprecated) {
                 this.addDiagnostic({
-                    ...DiagnosticMessages.deprecatedBrightScriptComponent(firstParamStringValue, brightScriptComponent.deprecatedDescription),
+                    ...DiagnosticMessages.itemIsDeprecated(firstParamStringValue, brightScriptComponent.deprecatedDescription),
                     location: call.location
                 });
             }
@@ -768,10 +768,11 @@ export class ScopeValidator {
             }
         } else if (isDynamicType(exprType) && isEnumType(parentTypeInfo?.type) && isDottedGetExpression(expression)) {
             const enumFileLink = this.event.scope.getEnumFileLink(util.getAllDottedGetPartsAsString(expression.obj));
+            const typeChainScanForItem = util.processTypeChain(typeChain);
             const typeChainScanForParent = util.processTypeChain(typeChain.slice(0, -1));
             if (enumFileLink) {
                 this.addMultiScopeDiagnostic({
-                    ...DiagnosticMessages.unknownEnumValue(lastTypeInfo?.name, typeChainScanForParent.fullChainName),
+                    ...DiagnosticMessages.cannotFindName(lastTypeInfo?.name, typeChainScanForItem.fullChainName, typeChainScanForParent.fullNameOfItem, 'enum'),
                     location: lastTypeInfo?.location,
                     relatedInformation: [{
                         message: 'Enum declared here',
@@ -1116,7 +1117,7 @@ export class ScopeValidator {
             const classStmtLink = this.event.scope.getClassFileLink(lowerVarName);
             if (classStmtLink) {
                 this.addMultiScopeDiagnostic({
-                    ...DiagnosticMessages.localVarSameNameAsClass(classStmtLink?.item?.getName(ParseMode.BrighterScript)),
+                    ...DiagnosticMessages.localVarShadowedByScopedFunction(),
                     location: util.createLocationFromFileRange(file, varDeclaration.nameRange),
                     relatedInformation: [{
                         message: 'Class declared here',
@@ -1196,7 +1197,7 @@ export class ScopeValidator {
             const foundType = docTypeTag.typeExpression?.getType({ flags: SymbolTypeFlag.typetime });
             if (!foundType?.isResolvable()) {
                 this.addMultiScopeDiagnostic({
-                    ...DiagnosticMessages.cannotFindTypeInCommentDoc(docTypeTag.typeString),
+                    ...DiagnosticMessages.cannotFindName(docTypeTag.typeString),
                     location: brsDocParser.getTypeLocationFromToken(docTypeTag.token) ?? docTypeTag.location
                 });
             }
diff --git a/src/diagnosticUtils.ts b/src/diagnosticUtils.ts
index 0fb043b3..ddf269a8 100644
--- a/src/diagnosticUtils.ts
+++ b/src/diagnosticUtils.ts
@@ -72,6 +72,8 @@ export function printDiagnostic(
     let severityText = severityTextMap[severity];
 
     console.log('');
+    const printableDiagnosticCode = diagnostic.code ? diagnostic.code.toString() : 'BS' + ((diagnostic as BsDiagnostic).legacyCode ?? '');
+
     console.log(
         chalk.cyan(filePath ?? '<unknown file>') +
         ':' +
@@ -83,7 +85,7 @@ export function printDiagnostic(
         ' - ' +
         typeColor[severity](severityText) +
         ' ' +
-        chalk.grey('BS' + diagnostic.code) +
+        chalk.grey(printableDiagnosticCode) +
         ': ' +
         chalk.white(diagnostic.message)
     );
diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts
index 6a285907..2227241e 100644
--- a/src/files/BrsFile.Class.spec.ts
+++ b/src/files/BrsFile.Class.spec.ts
@@ -1281,20 +1281,6 @@ describe('BrsFile BrighterScript classes', () => {
         ]);
     });
 
-    it.skip('detects calls to unknown m methods', () => {
-        program.setFile('source/main.bs', `
-            class Animal
-                sub new()
-                    m.methodThatDoesNotExist()
-                end sub
-            end class
-        `);
-        program.validate();
-        expectDiagnostics(program, [
-            DiagnosticMessages.methodDoesNotExistOnType('methodThatDoesNotExist', 'Animal')
-        ]);
-    });
-
     it('detects direct circular extends', () => {
         //direct
         program.setFile('source/Direct.bs', `
@@ -1431,7 +1417,7 @@ describe('BrsFile BrighterScript classes', () => {
         `);
         program.validate();
         expectDiagnostics(program, [
-            DiagnosticMessages.classChildMemberDifferentMemberTypeThanAncestor('method', 'field', 'Animal')
+            DiagnosticMessages.childFieldTypeNotAssignableToBaseProperty('Duck', 'Animal', 'name', 'function name() as dynamic', 'dynamic')
         ]);
     });
 
@@ -1789,7 +1775,7 @@ describe('BrsFile BrighterScript classes', () => {
         `);
         program.validate();
         expectDiagnostics(program, [
-            DiagnosticMessages.localVarSameNameAsClass('Animal').message
+            DiagnosticMessages.localVarShadowedByScopedFunction().message
         ]);
     });
 
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index 18d6e5ff..7a72616b 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -434,6 +434,18 @@ describe('BrsFile', () => {
                 }]);
             });
 
+            it('recognizes diagnostic names', () => {
+                let file = program.setFile<BrsFile>({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
+                    sub Main()
+                        'bs:disable-next-line: cannot-find-name
+                        name = unknown
+                    end sub
+                `);
+                expect(file.commentFlags[0]).to.exist;
+                program.validate();
+                expectZeroDiagnostics(program);
+            });
+
         });
 
         describe('bs:disable-line', () => {
@@ -732,7 +744,7 @@ describe('BrsFile', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.referencedConstDoesNotExist()
+                    DiagnosticMessages.hashConstDoesNotExist()
                 ]);
             });
 
@@ -746,7 +758,7 @@ describe('BrsFile', () => {
                 `);
                 program.validate();
                 expectDiagnostics(program, [
-                    DiagnosticMessages.referencedConstDoesNotExist()
+                    DiagnosticMessages.hashConstDoesNotExist()
                 ]);
             });
 
@@ -771,7 +783,7 @@ describe('BrsFile', () => {
                     end sub
                 `);
                 expectDiagnostics(program, [
-                    DiagnosticMessages.constNameCannotBeReservedWord()
+                    DiagnosticMessages.cannotUseReservedWordAsIdentifier('function')
                 ]);
             });
 
@@ -1179,7 +1191,7 @@ describe('BrsFile', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('import')
+                DiagnosticMessages.unexpectedStatementLocation('import', 'at the top of the file')
             ]);
         });
 
@@ -1198,7 +1210,7 @@ describe('BrsFile', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('library')
+                DiagnosticMessages.unexpectedStatementLocation('library', 'at the top of the file')
             ]);
         });
 
@@ -1210,7 +1222,7 @@ describe('BrsFile', () => {
             `);
             program.validate();
             expectDiagnostics(program, [
-                DiagnosticMessages.statementMustBeDeclaredAtTopOfFile('library')
+                DiagnosticMessages.unexpectedStatementLocation('library', 'at the top of the file')
             ]);
         });
 
@@ -1319,10 +1331,10 @@ describe('BrsFile', () => {
                 end function
             `);
             expectDiagnostics(file.parser.diagnostics, [
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments'),
                 DiagnosticMessages.expectedNewlineOrColon(),
                 DiagnosticMessages.unexpectedToken('end function'),
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments'),
                 DiagnosticMessages.expectedNewlineOrColon()
             ]);
         });
@@ -4846,10 +4858,10 @@ describe('BrsFile', () => {
         `);
         program.validate();
         expectDiagnostics(program, [{
-            ...DiagnosticMessages.mismatchedEndCallableKeyword('function', 'sub'),
+            ...DiagnosticMessages.closingKeywordMismatch('function', 'sub'),
             location: util.createLocationFromFileRange(file, util.createRange(2, 12, 2, 19))
         }, {
-            ...DiagnosticMessages.mismatchedEndCallableKeyword('sub', 'function'),
+            ...DiagnosticMessages.closingKeywordMismatch('sub', 'function'),
             location: util.createLocationFromFileRange(file, util.createRange(4, 12, 4, 24))
         }]);
     });
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index 1aee5d54..d0a331f3 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -6,7 +6,7 @@ import { CompletionItemKind } from 'vscode-languageserver';
 import chalk from 'chalk';
 import * as path from 'path';
 import { Scope } from '../Scope';
-import { DiagnosticCodeMap, diagnosticCodes, DiagnosticMessages } from '../DiagnosticMessages';
+import { DiagnosticCodeMap, diagnosticCodes, DiagnosticLegacyCodeMap, DiagnosticMessages } from '../DiagnosticMessages';
 import { FunctionScope } from '../FunctionScope';
 import type { Callable, CallableParam, CommentFlag, BsDiagnostic, FileReference, FileLink, SerializedCodeFile, NamespaceContainer } from '../interfaces';
 import type { Token } from '../lexer/Token';
@@ -442,7 +442,7 @@ export class BrsFile implements BscFile {
      * @param tokens - an array of tokens of which to find `TokenKind.Comment` from
      */
     public getCommentFlags(tokens: Token[]) {
-        const processor = new CommentFlagProcessor(this, ['rem', `'`], diagnosticCodes, [DiagnosticCodeMap.unknownDiagnosticCode]);
+        const processor = new CommentFlagProcessor(this, ['rem', `'`], diagnosticCodes, [DiagnosticCodeMap.unknownDiagnosticCode, DiagnosticLegacyCodeMap.unknownDiagnosticCode]);
 
         this.commentFlags = [];
         for (let lexerToken of tokens) {
diff --git a/src/files/XmlFile.spec.ts b/src/files/XmlFile.spec.ts
index ed494fb6..061ea7f1 100644
--- a/src/files/XmlFile.spec.ts
+++ b/src/files/XmlFile.spec.ts
@@ -160,10 +160,10 @@ describe('XmlFile', () => {
                 </component>`
             );
             expectDiagnostics(program, [{ // expecting opening tag but got prolog
-                code: DiagnosticMessages.xmlGenericParseError('').code,
+                code: DiagnosticMessages.syntaxError('').code,
                 location: { range: Range.create(1, 16, 1, 22) }
             }, {
-                ...DiagnosticMessages.xmlGenericParseError('Syntax error: whitespace found before the XML prolog'),
+                ...DiagnosticMessages.syntaxError('Syntax error: whitespace found before the XML prolog'),
                 location: { range: Range.create(0, 0, 1, 16) }
             }]);
         });
@@ -223,7 +223,7 @@ describe('XmlFile', () => {
             program.validate();
             const diagnostics = program.getDiagnostics();
             expect(diagnostics).to.be.lengthOf(2);
-            expect(diagnostics[0].code).to.equal(DiagnosticMessages.xmlGenericParseError('').code); //unexpected character '1'
+            expect(diagnostics[0].code).to.equal(DiagnosticMessages.syntaxError('').code); //unexpected character '1'
             expect(diagnostics[1]).to.deep.include(<BsDiagnostic>{
                 code: DiagnosticMessages.xmlComponentMissingNameAttribute().code,
                 location: util.createLocationFromFileRange(file, Range.create(1, 1, 1, 10))
diff --git a/src/files/XmlFile.ts b/src/files/XmlFile.ts
index d1e2aeac..56d8de49 100644
--- a/src/files/XmlFile.ts
+++ b/src/files/XmlFile.ts
@@ -2,7 +2,7 @@ import * as path from 'path';
 import type { CodeWithSourceMap } from 'source-map';
 import { SourceNode } from 'source-map';
 import type { Location, Position, Range } from 'vscode-languageserver';
-import { DiagnosticCodeMap, diagnosticCodes } from '../DiagnosticMessages';
+import { DiagnosticCodeMap, DiagnosticLegacyCodeMap, diagnosticCodes } from '../DiagnosticMessages';
 import type { Callable, FileReference, CommentFlag, SerializedCodeFile } from '../interfaces';
 import type { Program } from '../Program';
 import util from '../util';
@@ -279,7 +279,7 @@ export class XmlFile implements BscFile {
      * Collect all bs: comment flags
      */
     public getCommentFlags(tokens: Array<IToken & { tokenType: TokenType }>) {
-        const processor = new CommentFlagProcessor(this, ['<!--'], diagnosticCodes, [DiagnosticCodeMap.unknownDiagnosticCode]);
+        const processor = new CommentFlagProcessor(this, ['<!--'], diagnosticCodes, [DiagnosticCodeMap.unknownDiagnosticCode, DiagnosticLegacyCodeMap.unknownDiagnosticCode]);
 
         this.commentFlags = [];
         for (let token of tokens) {
diff --git a/src/interfaces.ts b/src/interfaces.ts
index a9483aea..36de3cc0 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -72,6 +72,11 @@ export interface BsDiagnostic {
      * @since 3.16.0
      */
     data?: any;
+
+    /**
+     * The code used for this diagnostic in v0
+     */
+    legacyCode?: number | string;
 }
 
 export interface DiagnosticContext {
diff --git a/src/lexer/Lexer.spec.ts b/src/lexer/Lexer.spec.ts
index 28972fc6..1b06a5d0 100644
--- a/src/lexer/Lexer.spec.ts
+++ b/src/lexer/Lexer.spec.ts
@@ -8,6 +8,7 @@ import { isToken } from './Token';
 import { rangeToArray } from '../parser/Parser.spec';
 import { Range } from 'vscode-languageserver';
 import util from '../util';
+import { DiagnosticMessages } from '../DiagnosticMessages';
 
 describe('lexer', () => {
     it('recognizes the `const` keyword', () => {
@@ -429,7 +430,7 @@ describe('lexer', () => {
         it('disallows multiline strings', () => {
             let { diagnostics } = Lexer.scan(`"multi-line\n\n`);
             expect(diagnostics.map(err => err.message)).to.deep.equal([
-                'Unterminated string at end of line'
+                DiagnosticMessages.unterminatedString().message
             ]);
         });
     });
@@ -1374,7 +1375,6 @@ describe('lexer', () => {
                 /simple/,
                 /SimpleWithValidFlags/g,
                 /UnknownFlags/gi,
-                /with spaces/s,
                 /with(parens)and[squarebraces]/,
                 //lots of special characters
                 /.*()^$@/,
diff --git a/src/lexer/Lexer.ts b/src/lexer/Lexer.ts
index e3e21770..3fc37e76 100644
--- a/src/lexer/Lexer.ts
+++ b/src/lexer/Lexer.ts
@@ -508,7 +508,7 @@ export class Lexer {
             if (this.peekNext() === '\n' || this.peekNext() === '\r') {
                 // BrightScript doesn't support multi-line strings
                 this.diagnostics.push({
-                    ...DiagnosticMessages.unterminatedStringAtEndOfLine(),
+                    ...DiagnosticMessages.unterminatedString(),
                     location: this.locationOf()
                 });
                 isUnterminated = true;
@@ -521,7 +521,7 @@ export class Lexer {
         if (this.isAtEnd()) {
             // terminating a string with EOF is also not allowed
             this.diagnostics.push({
-                ...DiagnosticMessages.unterminatedStringAtEndOfFile(),
+                ...DiagnosticMessages.unterminatedString(),
                 location: this.locationOf()
             });
             isUnterminated = true;
diff --git a/src/parser/Parser.Class.spec.ts b/src/parser/Parser.Class.spec.ts
index 5dbc81d5..2f1c2e01 100644
--- a/src/parser/Parser.Class.spec.ts
+++ b/src/parser/Parser.Class.spec.ts
@@ -174,7 +174,7 @@ describe('parser class', () => {
             `);
         let { ast, diagnostics } = Parser.parse(tokens, { mode: ParseMode.BrighterScript });
         expect(diagnostics).length.to.be.greaterThan(0);
-        expect(diagnostics[0].code).to.equal(DiagnosticMessages.expectedIdentifierAfterKeyword('class').code);
+        expect(diagnostics[0].code).to.equal(DiagnosticMessages.expectedIdentifier('class').code);
         expect(ast.statements[0]).instanceof(ClassStatement);
     });
 
@@ -240,7 +240,7 @@ describe('parser class', () => {
             expect(diagnostics.length).to.be.greaterThan(0);
             let cls = ast.statements[0] as ClassStatement;
             expect(cls.fields[0].tokens.name!.text).to.equal('middleName');
-            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedIdentifierAfterKeyword('as'));
+            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedIdentifier('as'));
         });
 
         it('field access modifier defaults to undefined when omitted', () => {
@@ -384,7 +384,7 @@ describe('parser class', () => {
             end class
         `);
         let { diagnostics } = Parser.parse(tokens, { mode: ParseMode.BrighterScript });
-        expect(diagnostics[0].code).to.equal(DiagnosticMessages.expectedIdentifierAfterKeyword('extends').code);
+        expect(diagnostics[0].code).to.equal(DiagnosticMessages.expectedIdentifier('extends').code);
     });
 
     describe('new keyword', () => {
diff --git a/src/parser/Parser.spec.ts b/src/parser/Parser.spec.ts
index 97d21d5c..36d9619f 100644
--- a/src/parser/Parser.spec.ts
+++ b/src/parser/Parser.spec.ts
@@ -52,6 +52,23 @@ describe('parser', () => {
         });
     });
 
+    it('flags function names with invalid characters', () => {
+        const { diagnostics } = Parser.parse(`
+            function alpha$() : end function
+            function beta%() : end function
+            function charlie!() : end function
+            function delta#() : end function
+            function echo&() : end function
+        `);
+        expectDiagnostics(diagnostics, [
+            DiagnosticMessages.invalidIdentifier('alpha$', '$'),
+            DiagnosticMessages.invalidIdentifier('beta%', '%'),
+            DiagnosticMessages.invalidIdentifier('charlie!', '!'),
+            DiagnosticMessages.invalidIdentifier('delta#', '#'),
+            DiagnosticMessages.invalidIdentifier('echo&', '&')
+        ]);
+    });
+
     describe('optional chaining operator', () => {
         function getExpression<T>(text: string, options?: { matcher?: any; parseMode?: ParseMode }) {
             const parser = parse(text, options?.parseMode);
@@ -164,7 +181,7 @@ describe('parser', () => {
                 end functionasdf
             `).diagnostics;
             expect(diagnostics[0]?.message).to.exist.and.to.eql(
-                DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression().message
+                DiagnosticMessages.expectedStatement().message
             );
 
             expect(diagnostics[0]?.location.range).to.eql(
@@ -239,6 +256,41 @@ describe('parser', () => {
             ).to.be.true;
         });
 
+        it('adds diagnostics when missing end for statements', () => {
+            let parser = parse(`
+                sub test1(x)
+                    for each item in x
+                        print item
+                 end sub
+            `, ParseMode.BrightScript);
+            expectDiagnosticsIncludes(parser, [
+                DiagnosticMessages.expectedEndForOrNextToTerminateForLoop('for each')
+            ]);
+
+            parser = parse(`
+                sub test2()
+                    for i = 0 to 10
+                        print i
+                 end sub
+            `, ParseMode.BrightScript);
+            expectDiagnosticsIncludes(parser, [
+                DiagnosticMessages.expectedEndForOrNextToTerminateForLoop('for')
+            ]);
+
+            parser = parse(`
+                sub test3(x )
+                    for each item in x
+                        print item
+                    next
+
+                    for i = 0 to 10
+                        print i
+                    next ' next works the same as "end for"
+                 end sub
+            `, ParseMode.BrightScript);
+            expectZeroDiagnostics(parser);
+        });
+
         describe('namespace', () => {
             it('allows namespaces declared inside other namespaces', () => {
                 const parser = parse(`
@@ -302,7 +354,7 @@ describe('parser', () => {
                         end namespace
                     `, ParseMode.BrighterScript);
                 expect(diagnostics[0]?.message).to.equal(
-                    DiagnosticMessages.expectedIdentifierAfterKeyword('namespace').message
+                    DiagnosticMessages.expectedIdentifier('namespace').message
                 );
             });
 
@@ -481,7 +533,7 @@ describe('parser', () => {
             let { tokens } = Lexer.scan(`print a.`);
             let { ast, diagnostics } = Parser.parse(tokens);
             let printStatement = ast.statements[0] as PrintStatement;
-            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedPropertyNameAfterPeriod());
+            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedIdentifier());
             expect(printStatement).to.be.instanceof(PrintStatement);
             expect(printStatement.expressions[0]).to.be.instanceof(VariableExpression);
         });
@@ -490,7 +542,7 @@ describe('parser', () => {
             let { tokens } = Lexer.scan(`print a.b.`);
             let { ast, diagnostics } = Parser.parse(tokens);
             let printStatement = ast.statements[0] as PrintStatement;
-            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedPropertyNameAfterPeriod());
+            expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedIdentifier());
             expect(printStatement).to.be.instanceof(PrintStatement);
             expect(printStatement.expressions[0]).to.be.instanceof(DottedGetExpression);
         });
@@ -508,7 +560,7 @@ describe('parser', () => {
                     end namespace
                 `);
                 let { ast, diagnostics } = Parser.parse(tokens) as any;
-                expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression());
+                expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedStatement());
                 let stmt = ast.statements[0].func.body.statements[0];
 
                 expect(isExpressionStatement(stmt)).to.be.true;
@@ -590,7 +642,7 @@ describe('parser', () => {
                     end function
                 `);
                 let { ast, diagnostics } = Parser.parse(tokens) as any;
-                expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression());
+                expectDiagnosticsIncludes(diagnostics, DiagnosticMessages.expectedStatement());
                 for (const stmt of ast.statements[0].func.body.statements) {
                     expect(isExpressionStatement(stmt)).to.be.true;
                     expect(isBinaryExpression((stmt).expression)).to.be.true;
@@ -1397,7 +1449,7 @@ describe('parser', () => {
                     print param
                 end sub
             `, ParseMode.BrightScript);
-            expectDiagnosticsIncludes(parser.diagnostics, [DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression()]);
+            expectDiagnosticsIncludes(parser.diagnostics, [DiagnosticMessages.expectedStatement()]);
         });
 
         it('allows union types in parameters', () => {
@@ -2145,7 +2197,7 @@ describe('parser', () => {
                 end sub
             `, ParseMode.BrighterScript, { debug: true });
             expectDiagnostics(diagnostics, [
-                DiagnosticMessages.expectedEndIfToCloseIfStatement({ line: 3, character: 20 }).message,
+                DiagnosticMessages.expectedTerminator('end if', 'if').message,
                 DiagnosticMessages.unexpectedToken('end if').message
             ]);
         });
@@ -2224,7 +2276,7 @@ describe('parser', () => {
                     #const test
                 `, ParseMode.BrighterScript);
                 expectDiagnostics(diagnostics, [
-                    DiagnosticMessages.expectedOperatorAfterIdentifier([TokenKind.Equal], 'test').message
+                    DiagnosticMessages.expectedOperator([TokenKind.Equal], 'test').message
                 ]);
             });
 
@@ -2233,7 +2285,7 @@ describe('parser', () => {
                     #const test += other
                 `, ParseMode.BrighterScript);
                 expectDiagnostics(diagnostics, [
-                    DiagnosticMessages.expectedOperatorAfterIdentifier([TokenKind.Equal], 'test').message
+                    DiagnosticMessages.expectedOperator([TokenKind.Equal], 'test').message
                 ]);
             });
 
diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts
index 3c9c6869..520f6acb 100644
--- a/src/parser/Parser.ts
+++ b/src/parser/Parser.ts
@@ -370,7 +370,7 @@ export class Parser {
 
     private enumMemberStatement() {
         const name = this.consume(
-            DiagnosticMessages.expectedClassFieldIdentifier(),
+            DiagnosticMessages.expectedIdentifier(),
             TokenKind.Identifier,
             ...AllowedProperties
         ) as Identifier;
@@ -406,7 +406,7 @@ export class Parser {
             if (this.checkEndOfStatement()) {
                 if (!ignoreDiagnostics) {
                     this.diagnostics.push({
-                        ...DiagnosticMessages.expectedIdentifierAfterKeyword(asToken.text),
+                        ...DiagnosticMessages.expectedIdentifier(asToken.text),
                         location: asToken.location
                     });
                 }
@@ -415,7 +415,7 @@ export class Parser {
             } else if (this.peek().kind !== TokenKind.Identifier && !this.checkAny(...DeclarableTypes, ...AllowedTypeIdentifiers)) {
                 if (!ignoreDiagnostics) {
                     this.diagnostics.push({
-                        ...DiagnosticMessages.expectedIdentifierAfterKeyword(asToken.text),
+                        ...DiagnosticMessages.expectedIdentifier(asToken.text),
                         location: asToken.location
                     });
                 }
@@ -486,7 +486,7 @@ export class Parser {
             extendsToken = this.advance();
             if (this.checkEndOfStatement()) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.expectedIdentifierAfterKeyword(extendsToken.text),
+                    ...DiagnosticMessages.expectedIdentifier(extendsToken.text),
                     location: extendsToken.location
                 });
             } else {
@@ -634,14 +634,14 @@ export class Parser {
         let parentClassName: TypeExpression;
 
         //get the class name
-        let className = this.tryConsume(DiagnosticMessages.expectedIdentifierAfterKeyword('class'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
+        let className = this.tryConsume(DiagnosticMessages.expectedIdentifier('class'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
 
         //see if the class inherits from parent
         if (this.peek().text.toLowerCase() === 'extends') {
             extendsKeyword = this.advance();
             if (this.checkEndOfStatement()) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.expectedIdentifierAfterKeyword(extendsKeyword.text),
+                    ...DiagnosticMessages.expectedIdentifier(extendsKeyword.text),
                     location: extendsKeyword.location
                 });
             } else {
@@ -768,7 +768,7 @@ export class Parser {
         }
 
         let name = this.consume(
-            DiagnosticMessages.expectedClassFieldIdentifier(),
+            DiagnosticMessages.expectedIdentifier(),
             TokenKind.Identifier,
             ...AllowedProperties
         ) as Identifier;
@@ -838,17 +838,17 @@ export class Parser {
 
             if (isAnonymous) {
                 leftParen = this.consume(
-                    DiagnosticMessages.expectedLeftParenAfterCallable(functionTypeText),
+                    DiagnosticMessages.expectedToken('('),
                     TokenKind.LeftParen
                 );
             } else {
                 name = this.consume(
-                    DiagnosticMessages.expectedNameAfterCallableKeyword(functionTypeText),
+                    DiagnosticMessages.expectedIdentifier(functionTypeText),
                     TokenKind.Identifier,
                     ...AllowedProperties
                 ) as Identifier;
                 leftParen = this.consume(
-                    DiagnosticMessages.expectedLeftParenAfterCallableName(functionTypeText),
+                    DiagnosticMessages.expectedToken('('),
                     TokenKind.LeftParen
                 );
 
@@ -857,7 +857,7 @@ export class Parser {
                 if (['$', '%', '!', '#', '&'].includes(lastChar)) {
                     //don't throw this error; let the parser continue
                     this.diagnostics.push({
-                        ...DiagnosticMessages.functionNameCannotEndWithTypeDesignator(functionTypeText, name.text, lastChar),
+                        ...DiagnosticMessages.invalidIdentifier(name.text, lastChar),
                         location: name.location
                     });
                 }
@@ -911,7 +911,7 @@ export class Parser {
             //add an error but don't hard-fail so the AST can continue more gracefully
             if (endFunctionType.kind !== expectedEndKind) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.mismatchedEndCallableKeyword(functionTypeText, endFunctionType.text),
+                    ...DiagnosticMessages.closingKeywordMismatch(functionTypeText, endFunctionType.text),
                     location: endFunctionType.location
                 });
             }
@@ -1002,7 +1002,7 @@ export class Parser {
         }
 
         let operator = this.consume(
-            DiagnosticMessages.expectedOperatorAfterIdentifier([TokenKind.Equal], name.text),
+            DiagnosticMessages.expectedOperator([TokenKind.Equal], name.text),
             ...[TokenKind.Equal]
         );
         let value = this.expression();
@@ -1297,7 +1297,7 @@ export class Parser {
         let endForToken: Token;
         if (!body || !this.checkAny(TokenKind.EndFor, TokenKind.Next)) {
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(),
+                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(forToken.text),
                 location: this.peek().location
             });
             if (!body) {
@@ -1330,7 +1330,7 @@ export class Parser {
             this.advance();
         } else {
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedInAfterForEach(name.text),
+                ...DiagnosticMessages.expectedToken(TokenKind.In),
                 location: this.peek().location
             });
             throw this.lastDiagnosticAsError();
@@ -1349,20 +1349,21 @@ export class Parser {
         this.consumeStatementSeparators();
 
         let body = this.block(TokenKind.EndFor, TokenKind.Next);
-        if (!body) {
+        let endForToken: Token;
+        if (!body || !this.checkAny(TokenKind.EndFor, TokenKind.Next)) {
+
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(),
+                ...DiagnosticMessages.expectedEndForOrNextToTerminateForLoop(forEach.text),
                 location: this.peek().location
             });
             throw this.lastDiagnosticAsError();
         }
-
-        let endFor = this.advance();
+        endForToken = this.advance();
 
         return new ForEachStatement({
             forEach: forEach,
             in: maybeIn,
-            endFor: endFor,
+            endFor: endForToken,
             item: name,
             target: target,
             body: body
@@ -1414,7 +1415,7 @@ export class Parser {
     private identifyingExpression(allowedTokenKinds?: TokenKind[]): DottedGetExpression | VariableExpression {
         allowedTokenKinds = allowedTokenKinds ?? this.allowedLocalIdentifiers;
         let firstIdentifier = this.consume(
-            DiagnosticMessages.expectedIdentifierAfterKeyword(this.previous().text),
+            DiagnosticMessages.expectedIdentifier(this.previous().text),
             TokenKind.Identifier,
             ...allowedTokenKinds
         ) as Identifier;
@@ -1533,7 +1534,7 @@ export class Parser {
             });
         }
         this.diagnostics.push({
-            ...DiagnosticMessages.expectedIdentifierAfterKeyword('typecast'),
+            ...DiagnosticMessages.expectedIdentifier('typecast'),
             location: {
                 uri: typecastToken.location.uri,
                 range: util.createBoundingRange(typecastToken, this.peek())
@@ -1546,7 +1547,7 @@ export class Parser {
         this.warnIfNotBrighterScriptMode('alias statements');
         const aliasToken = this.advance();
         const name = this.tryConsume(
-            DiagnosticMessages.expectedIdentifierAfterKeyword('alias'),
+            DiagnosticMessages.expectedIdentifier('alias'),
             TokenKind.Identifier
         );
         const equals = this.tryConsume(
@@ -1708,7 +1709,7 @@ export class Parser {
         if (this.isAtEnd()) {
             //error - missing backtick
             this.diagnostics.push({
-                ...DiagnosticMessages.unterminatedTemplateStringAtEndOfFile(),
+                ...DiagnosticMessages.unterminatedTemplateString(),
                 location: {
                     uri: openingBacktick.location.uri,
                     range: util.createBoundingRange(openingBacktick, this.peek())
@@ -1774,7 +1775,7 @@ export class Parser {
         }
         if (this.peek().kind !== TokenKind.EndTry) {
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedEndTryToTerminateTryCatch(),
+                ...DiagnosticMessages.expectedTerminator('end try', 'try-catch'),
                 location: this.peek().location
             });
         } else {
@@ -1808,13 +1809,13 @@ export class Parser {
     private dimStatement() {
         const dim = this.advance();
 
-        let identifier = this.tryConsume(DiagnosticMessages.expectedIdentifierAfterKeyword('dim'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
+        let identifier = this.tryConsume(DiagnosticMessages.expectedIdentifier('dim'), TokenKind.Identifier, ...this.allowedLocalIdentifiers) as Identifier;
         // force to an identifier so the AST makes some sense
         if (identifier) {
             identifier.kind = TokenKind.Identifier;
         }
 
-        let leftSquareBracket = this.tryConsume(DiagnosticMessages.missingLeftSquareBracketAfterDimIdentifier(), TokenKind.LeftSquareBracket);
+        let leftSquareBracket = this.tryConsume(DiagnosticMessages.expectedToken('['), TokenKind.LeftSquareBracket);
 
         let expressions: Expression[] = [];
         let expression: Expression;
@@ -1838,7 +1839,7 @@ export class Parser {
                 location: this.peek().location
             });
         }
-        let rightSquareBracket = this.tryConsume(DiagnosticMessages.missingRightSquareBracketAfterDimIdentifier(), TokenKind.RightSquareBracket);
+        let rightSquareBracket = this.tryConsume(DiagnosticMessages.unmatchedLeftToken('[', 'dim identifier'), TokenKind.RightSquareBracket);
         return new DimStatement({
             dim: dim,
             name: identifier,
@@ -1892,7 +1893,7 @@ export class Parser {
 
             if (!thenBranch) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.expectedStatementToFollowConditionalCondition(ifToken.text),
+                    ...DiagnosticMessages.expectedStatement(ifToken.text, 'statement'),
                     location: this.peek().location
                 });
                 throw this.lastDiagnosticAsError();
@@ -1934,7 +1935,7 @@ export class Parser {
                 if (!elseBranch) {
                     //missing `else` branch
                     this.diagnostics.push({
-                        ...DiagnosticMessages.expectedStatementToFollowElse(),
+                        ...DiagnosticMessages.expectedStatement('else', 'statement'),
                         location: this.peek().location
                     });
                     throw this.lastDiagnosticAsError();
@@ -1989,7 +1990,7 @@ export class Parser {
                 } else {
                     //missing endif
                     this.diagnostics.push({
-                        ...DiagnosticMessages.expectedEndIfToCloseIfStatement(ifToken.location?.range.start),
+                        ...DiagnosticMessages.expectedTerminator('end if', 'if'),
                         location: ifToken.location
                     });
                 }
@@ -2025,7 +2026,7 @@ export class Parser {
 
             //this whole if statement is bogus...add error to the if token and hard-fail
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedEndIfElseIfOrElseToTerminateThenBlock(),
+                ...DiagnosticMessages.expectedTerminator(['end if', 'else if', 'else'], 'then', 'block'),
                 location: ifToken.location
             });
             throw this.lastDiagnosticAsError();
@@ -2098,7 +2099,7 @@ export class Parser {
             } else {
                 //missing #endif
                 this.diagnostics.push({
-                    ...DiagnosticMessages.expectedHashEndIfToCloseHashIf(hashIfToken.location?.range.start.line),
+                    ...DiagnosticMessages.expectedTerminator('#end if', '#if'),
                     location: hashIfToken.location
                 });
             }
@@ -2132,7 +2133,7 @@ export class Parser {
 
             //this whole if statement is bogus...add error to the if token and hard-fail
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedTerminatorOnConditionalCompileBlock(),
+                ...DiagnosticMessages.expectedTerminator(['#end if', '#else if', '#else'], 'conditional compilation', 'block'),
                 location: hashIfToken.location
             });
             throw this.lastDiagnosticAsError();
@@ -2227,7 +2228,7 @@ export class Parser {
         //disallow using keywords for const names
         if (ReservedWords.has(constName?.text.toLowerCase())) {
             this.diagnostics.push({
-                ...DiagnosticMessages.constNameCannotBeReservedWord(),
+                ...DiagnosticMessages.cannotUseReservedWordAsIdentifier(constName?.text),
                 location: constName?.location
             });
 
@@ -2261,7 +2262,7 @@ export class Parser {
 
         if (!this.check(TokenKind.Newline)) {
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedNewlineInConditionalCompile(),
+                ...DiagnosticMessages.unexpectedToken(this.peek().text),
                 location: this.peek().location
             });
             throw this.lastDiagnosticAsError();
@@ -2281,7 +2282,7 @@ export class Parser {
         //ensure newline before next keyword
         if (!this.check(TokenKind.Newline)) {
             this.diagnostics.push({
-                ...DiagnosticMessages.expectedNewlineInConditionalCompile(),
+                ...DiagnosticMessages.unexpectedToken(this.peek().text),
                 location: this.peek().location
             });
             throw this.lastDiagnosticAsError();
@@ -2360,13 +2361,13 @@ export class Parser {
 
             if (this.checkAny(TokenKind.PlusPlus, TokenKind.MinusMinus)) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.consecutiveIncrementDecrementOperatorsAreNotAllowed(),
+                    ...DiagnosticMessages.unexpectedOperator(),
                     location: this.peek().location
                 });
                 throw this.lastDiagnosticAsError();
             } else if (isCallExpression(expr)) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.incrementDecrementOperatorsAreNotAllowedAsResultOfFunctionCall(),
+                    ...DiagnosticMessages.unexpectedOperator(),
                     location: expressionStart.location
                 });
                 throw this.lastDiagnosticAsError();
@@ -2386,7 +2387,7 @@ export class Parser {
 
         //at this point, it's probably an error. However, we recover a little more gracefully by creating an inclosing ExpressionStatement
         this.diagnostics.push({
-            ...DiagnosticMessages.expectedStatementOrFunctionCallButReceivedExpression(),
+            ...DiagnosticMessages.expectedStatement(),
             location: expressionStart.location
         });
         return new ExpressionStatement({ expression: expr });
@@ -2816,7 +2817,7 @@ export class Parser {
         while (this.match(TokenKind.Newline)) { }
 
         const closingSquare = this.tryConsume(
-            DiagnosticMessages.expectedRightSquareBraceAfterArrayOrObjectIndex(),
+            DiagnosticMessages.unmatchedLeftToken(openingSquare.text, 'array or object index'),
             TokenKind.RightSquareBracket
         );
 
@@ -2864,7 +2865,7 @@ export class Parser {
         let methodName = this.consume(DiagnosticMessages.expectedIdentifier(), TokenKind.Identifier, ...AllowedProperties);
         // force it into an identifier so the AST makes some sense
         methodName.kind = TokenKind.Identifier;
-        let openParen = this.consume(DiagnosticMessages.expectedOpenParenToFollowCallfuncIdentifier(), TokenKind.LeftParen);
+        let openParen = this.consume(DiagnosticMessages.expectedToken(TokenKind.LeftParen), TokenKind.LeftParen);
         let call = this.finishCall(openParen, callee, false);
 
         return new CallfuncExpression({
@@ -2896,7 +2897,7 @@ export class Parser {
                 } else {
                     let dot = this.previous();
                     let name = this.tryConsume(
-                        DiagnosticMessages.expectedPropertyNameAfterPeriod(),
+                        DiagnosticMessages.expectedIdentifier(),
                         TokenKind.Identifier,
                         ...AllowedProperties
                     );
@@ -2962,7 +2963,7 @@ export class Parser {
         while (this.match(TokenKind.Newline)) { }
 
         const closingParen = this.tryConsume(
-            DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
+            DiagnosticMessages.unmatchedLeftToken(openingParen.text, 'function call arguments'),
             TokenKind.RightParen
         );
 
@@ -3094,7 +3095,7 @@ export class Parser {
                 let left = this.previous();
                 let expr = this.expression();
                 let right = this.consume(
-                    DiagnosticMessages.unmatchedLeftParenAfterExpression(),
+                    DiagnosticMessages.unmatchedLeftToken(left.text, 'expression'),
                     TokenKind.RightParen
                 );
                 return new GroupingExpression({ leftParen: left, rightParen: right, expression: expr });
@@ -3162,7 +3163,7 @@ export class Parser {
             }
 
             closingSquare = this.tryConsume(
-                DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral(),
+                DiagnosticMessages.unmatchedLeftToken(openingSquare.text, 'array literal'),
                 TokenKind.RightSquareBracket
             );
         } else {
@@ -3243,7 +3244,7 @@ export class Parser {
             }
 
             closingBrace = this.tryConsume(
-                DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral(),
+                DiagnosticMessages.unmatchedLeftToken(openingBrace.text, 'associative array literal'),
                 TokenKind.RightCurlyBrace
             );
         } else {
diff --git a/src/parser/SGParser.spec.ts b/src/parser/SGParser.spec.ts
index 9c70b6c4..851287f7 100644
--- a/src/parser/SGParser.spec.ts
+++ b/src/parser/SGParser.spec.ts
@@ -143,11 +143,12 @@ describe('SGParser', () => {
                 <script type="text/brightscript" uri="ChildScene.brs" />
             </component>`
         );
+        expect(parser.diagnostics).to.be.lengthOf(2);
         expectDiagnostics(parser, [{ // expecting opening tag but got prolog
-            code: DiagnosticMessages.xmlGenericParseError('').code,
+            code: DiagnosticMessages.syntaxError('Expecting token of type --> OPEN <-- but found --> \'<?xml \' <--').code,
             location: { range: Range.create(1, 12, 1, 18) }
         }, {
-            ...DiagnosticMessages.xmlGenericParseError('Syntax error: whitespace found before the XML prolog'),
+            ...DiagnosticMessages.syntaxError('Syntax error: whitespace found before the XML prolog'),
             location: { range: Range.create(0, 0, 1, 12) }
         }]);
     });
diff --git a/src/parser/SGParser.ts b/src/parser/SGParser.ts
index 4a0e09d3..f1a5fe87 100644
--- a/src/parser/SGParser.ts
+++ b/src/parser/SGParser.ts
@@ -99,7 +99,7 @@ export default class SGParser {
         if (lexErrors.length) {
             for (const err of lexErrors) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.xmlGenericParseError(`Syntax error: ${err.message}`),
+                    ...DiagnosticMessages.syntaxError(`Syntax error: ${err.message}`),
                     location: this.getLocationFromRange(util.createRange(
                         err.line - 1,
                         err.column,
@@ -113,7 +113,7 @@ export default class SGParser {
             const err = parseErrors[0];
             const token = err.token;
             this.diagnostics.push({
-                ...DiagnosticMessages.xmlGenericParseError(`Syntax error: ${err.message}`),
+                ...DiagnosticMessages.syntaxError(`Syntax error: ${err.message}`),
                 location: this.getLocationFromRange(!isNaN(token.startLine) ? this.rangeFromToken(token) : util.createRange(0, 0, 0, Number.MAX_VALUE))
             });
         }
@@ -128,7 +128,7 @@ export default class SGParser {
                 token2?.image.trim() === '<?xml'
             ) {
                 this.diagnostics.push({
-                    ...DiagnosticMessages.xmlGenericParseError('Syntax error: whitespace found before the XML prolog'),
+                    ...DiagnosticMessages.syntaxError('Syntax error: whitespace found before the XML prolog'),
                     location: this.getLocationFromRange(this.rangeFromToken(token1))
                 });
             }
diff --git a/src/util.ts b/src/util.ts
index 55b3f5b8..a871e865 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -205,7 +205,7 @@ export class Util {
             if (parseErrors.length > 0) {
                 let err = parseErrors[0];
                 let diagnostic = {
-                    ...DiagnosticMessages.bsConfigJsonHasSyntaxErrors(printParseErrorCode(parseErrors[0].error)),
+                    ...DiagnosticMessages.syntaxError(`Syntax errors in bsconfig.json: ${printParseErrorCode(parseErrors[0].error)}`),
                     location: {
                         uri: this.pathToUri(configFilePath),
                         range: this.getRangeFromOffsetLength(projectFileContents, err.offset, err.length)
@@ -1871,7 +1871,7 @@ export class Util {
                 return clone;
                 //filter out null relatedInformation items
             }).filter((x): x is DiagnosticRelatedInformation => Boolean(x)),
-            code: diagnostic.code,
+            code: diagnostic.code ? diagnostic.code : (diagnostic as BsDiagnostic).legacyCode,
             source: 'brs'
         } as Diagnostic;
         if (diagnostic?.tags?.length > 0) {
diff --git a/src/validators/ClassValidator.ts b/src/validators/ClassValidator.ts
index 23252f00..6f5ca63c 100644
--- a/src/validators/ClassValidator.ts
+++ b/src/validators/ClassValidator.ts
@@ -141,11 +141,20 @@ export class BsClassValidator {
 
                         //mismatched member type (field/method in child, opposite in ancestor)
                         if (memberType !== ancestorMemberKind) {
+                            const childFieldType = member.getType({ flags: SymbolTypeFlag.typetime });
+                            let ancestorMemberType: BscType = new DynamicType();
+                            if (isFieldStatement(ancestorAndMember.member)) {
+                                ancestorMemberType = ancestorAndMember.member.getType({ flags: SymbolTypeFlag.typetime });
+                            } else if (isMethodStatement(ancestorAndMember.member)) {
+                                ancestorMemberType = ancestorAndMember.member.func.getType({ flags: SymbolTypeFlag.typetime });
+                            }
                             this.diagnostics.push({
-                                ...DiagnosticMessages.classChildMemberDifferentMemberTypeThanAncestor(
-                                    memberType,
-                                    ancestorMemberKind,
-                                    ancestorAndMember.classStatement.getName(ParseMode.BrighterScript)
+                                ...DiagnosticMessages.childFieldTypeNotAssignableToBaseProperty(
+                                    classStatement.getName(ParseMode.BrighterScript) ?? '',
+                                    ancestorAndMember.classStatement.getName(ParseMode.BrighterScript),
+                                    memberName.text,
+                                    childFieldType.toString(),
+                                    ancestorMemberType.toString()
                                 ),
                                 location: member.location
                             });


Test patch:
diff --git a/src/parser/tests/controlFlow/If.spec.ts b/src/parser/tests/controlFlow/If.spec.ts
index 5bb549c7..ade0345b 100644
--- a/src/parser/tests/controlFlow/If.spec.ts
+++ b/src/parser/tests/controlFlow/If.spec.ts
@@ -361,7 +361,7 @@ describe('parser if statements', () => {
                     if x print 1 else if y : print 2
                 `);
                 expectDiagnosticsIncludes(diagnostics, [
-                    DiagnosticMessages.expectedEndIfElseIfOrElseToTerminateThenBlock().message
+                    DiagnosticMessages.expectedTerminator(['end if', 'else if', 'else'], 'then', 'block').message
                 ]);
             });
 
diff --git a/src/parser/tests/expression/ArrayLiterals.spec.ts b/src/parser/tests/expression/ArrayLiterals.spec.ts
index d4f5ef68..8ef980d8 100644
--- a/src/parser/tests/expression/ArrayLiterals.spec.ts
+++ b/src/parser/tests/expression/ArrayLiterals.spec.ts
@@ -180,7 +180,7 @@ describe('parser array literals', () => {
             // no closing brace:
             let { ast, diagnostics } = Parser.parse(`_ = [1, data.foo`);
 
-            expectDiagnostics(diagnostics, [DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral()]);
+            expectDiagnostics(diagnostics, [DiagnosticMessages.unmatchedLeftToken('[', 'array literal')]);
             expect(ast.statements).to.be.lengthOf(1);
             expect(isAssignmentStatement(ast.statements[0])).to.be.true;
             const assignStmt = ast.statements[0] as AssignmentStatement;
@@ -197,7 +197,7 @@ describe('parser array literals', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral()
+                DiagnosticMessages.unmatchedLeftToken('[', 'array literal')
             ]);
         });
 
@@ -208,7 +208,7 @@ describe('parser array literals', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral()
+                DiagnosticMessages.unmatchedLeftToken('[', 'array literal')
             ]);
         });
     });
diff --git a/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts b/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
index 646c9d12..194cf419 100644
--- a/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
+++ b/src/parser/tests/expression/AssociativeArrayLiterals.spec.ts
@@ -210,7 +210,7 @@ describe('parser associative array literals', () => {
         it('will still be parsed', () => {
             // No closing brace:
             let { ast, diagnostics } = Parser.parse(`_ = {name: "john", age: 42, address: data.address`);
-            expectDiagnostics(diagnostics, [DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral()]);
+            expectDiagnostics(diagnostics, [DiagnosticMessages.unmatchedLeftToken('{', 'associative array literal')]);
             expect(ast.statements).to.be.lengthOf(1);
             expect(isAssignmentStatement(ast.statements[0])).to.be.true;
             const assignStmt = ast.statements[0] as AssignmentStatement;
@@ -231,7 +231,7 @@ describe('parser associative array literals', () => {
             `);
             expectDiagnostics(diagnostics, [
                 DiagnosticMessages.unexpectedToken('\n'),
-                DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral()
+                DiagnosticMessages.unmatchedLeftToken('{', 'associative array literal')
             ]);
         });
 
@@ -243,7 +243,7 @@ describe('parser associative array literals', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral()
+                DiagnosticMessages.unmatchedLeftToken('{', 'associative array literal')
             ]);
         });
     });
diff --git a/src/parser/tests/expression/Call.spec.ts b/src/parser/tests/expression/Call.spec.ts
index 82b95bdc..99ed2129 100644
--- a/src/parser/tests/expression/Call.spec.ts
+++ b/src/parser/tests/expression/Call.spec.ts
@@ -56,7 +56,7 @@ describe('parser call expressions', () => {
         //there should only be 1 error
         expectDiagnostics(diagnostics, [
             DiagnosticMessages.unexpectedToken(':'),
-            DiagnosticMessages.expectedRightParenAfterFunctionCallArguments()
+            DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments')
         ]);
         expect(ast.statements).to.be.length.greaterThan(0);
         //the error should be BEFORE the `name = "bob"` statement
@@ -89,10 +89,10 @@ describe('parser call expressions', () => {
 
         expect(diagnostics).to.be.lengthOf(4);
         expectDiagnostics(diagnostics, [
-            DiagnosticMessages.expectedPropertyNameAfterPeriod(),
-            DiagnosticMessages.expectedPropertyNameAfterPeriod(),
-            DiagnosticMessages.expectedPropertyNameAfterPeriod(),
-            DiagnosticMessages.expectedPropertyNameAfterPeriod()
+            DiagnosticMessages.expectedIdentifier(),
+            DiagnosticMessages.expectedIdentifier(),
+            DiagnosticMessages.expectedIdentifier(),
+            DiagnosticMessages.expectedIdentifier()
         ]);
         expect(ast.statements).to.be.lengthOf(1);
         const bodyStatements = (ast.statements[0] as FunctionStatement).func.body.statements;
@@ -218,7 +218,7 @@ describe('parser call expressions', () => {
 
             expect(diagnostics).to.be.lengthOf(2);
             expectDiagnostics(diagnostics, [
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments'),
                 DiagnosticMessages.expectedNewlineOrColon()
             ]);
             expect(ast.statements).to.be.lengthOf(1);
@@ -244,7 +244,7 @@ describe('parser call expressions', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments()
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments')
             ]);
         });
 
@@ -255,7 +255,7 @@ describe('parser call expressions', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments()
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments')
             ]);
         });
 
@@ -266,7 +266,7 @@ describe('parser call expressions', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.expectedRightParenAfterFunctionCallArguments()
+                DiagnosticMessages.unmatchedLeftToken('(', 'function call arguments')
             ]);
             expect(ast.statements).to.be.lengthOf(1);
             const bodyStatements = (ast.statements[0] as FunctionStatement).func.body.statements;
diff --git a/src/parser/tests/expression/Indexing.spec.ts b/src/parser/tests/expression/Indexing.spec.ts
index 41209049..fb8d7ec1 100644
--- a/src/parser/tests/expression/Indexing.spec.ts
+++ b/src/parser/tests/expression/Indexing.spec.ts
@@ -81,7 +81,7 @@ describe('parser indexing', () => {
 
                 expect(diagnostics.length).to.equal(3);
                 expectDiagnostics(diagnostics, [
-                    DiagnosticMessages.expectedPropertyNameAfterPeriod(), // expected name after first dot
+                    DiagnosticMessages.expectedIdentifier(), // expected name after first dot
                     DiagnosticMessages.expectedNewlineOrColon(), // expected newline after "_ = foo" statement
                     DiagnosticMessages.unexpectedToken('.') // everything after the 2nd dot is ignored
                 ]);
@@ -307,7 +307,7 @@ describe('parser indexing', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.expectedRightSquareBraceAfterArrayOrObjectIndex()
+                DiagnosticMessages.unmatchedLeftToken('[', 'array or object index')
             ]);
         });
 
@@ -318,7 +318,7 @@ describe('parser indexing', () => {
                 end sub
             `);
             expectDiagnosticsIncludes(diagnostics, [
-                DiagnosticMessages.expectedRightSquareBraceAfterArrayOrObjectIndex()
+                DiagnosticMessages.unmatchedLeftToken('[', 'array or object index')
             ]);
         });
     });
diff --git a/src/parser/tests/expression/TemplateStringExpression.spec.ts b/src/parser/tests/expression/TemplateStringExpression.spec.ts
index a38dd2ca..e321a3b5 100644
--- a/src/parser/tests/expression/TemplateStringExpression.spec.ts
+++ b/src/parser/tests/expression/TemplateStringExpression.spec.ts
@@ -90,7 +90,7 @@ describe('TemplateStringExpression', () => {
         it('catches missing closing backtick', () => {
             let { tokens } = Lexer.scan('name = `hello world');
             let parser = Parser.parse(tokens, { mode: ParseMode.BrighterScript });
-            expect(parser.diagnostics[0]?.message).to.equal(DiagnosticMessages.unterminatedTemplateStringAtEndOfFile().message);
+            expect(parser.diagnostics[0]?.message).to.equal(DiagnosticMessages.unterminatedTemplateString().message);
         });
     });
 
diff --git a/src/parser/tests/statement/Dim.spec.ts b/src/parser/tests/statement/Dim.spec.ts
index 3c0f7c80..a1eeb593 100644
--- a/src/parser/tests/statement/Dim.spec.ts
+++ b/src/parser/tests/statement/Dim.spec.ts
@@ -2,6 +2,7 @@ import { expect } from '../../../chai-config.spec';
 import type { DimStatement } from '../../Statement';
 import { DiagnosticMessages } from '../../../DiagnosticMessages';
 import { Parser } from '../../Parser';
+import { expectDiagnostics } from '../../../testHelpers.spec';
 
 describe('parser DimStatement', () => {
     it('parses properly', () => {
@@ -31,7 +32,7 @@ describe('parser DimStatement', () => {
         //the statement should still exist and have null identifier
         expect(dimStatement).to.exist;
         expect(dimStatement.tokens.name).to.not.exist;
-        expect(parser.diagnostics.map(x => x.message)).to.include(DiagnosticMessages.expectedIdentifierAfterKeyword('dim').message);
+        expect(parser.diagnostics.map(x => x.message)).to.include(DiagnosticMessages.expectedIdentifier('dim').message);
     });
 
     it('flags missing left bracket', () => {
@@ -40,7 +41,11 @@ describe('parser DimStatement', () => {
         //the statement should still exist and have null dimensions
         expect(dimStatement).to.exist;
         expect(dimStatement.tokens.openingSquare).to.not.exist;
-        expect(parser.diagnostics.map(x => x.message)).to.include(DiagnosticMessages.missingLeftSquareBracketAfterDimIdentifier().message);
+        expectDiagnostics(parser, [
+            DiagnosticMessages.missingExpressionsInDimStatement().message,
+            DiagnosticMessages.expectedToken('[').message,
+            DiagnosticMessages.unexpectedToken(']').message
+        ]);
     });
 
     it('flags missing right bracket', () => {
@@ -49,7 +54,9 @@ describe('parser DimStatement', () => {
         //the statement should still exist and have null dimensions
         expect(dimStatement).to.exist;
         expect(dimStatement.tokens.closingSquare).to.not.exist;
-        expect(parser.diagnostics.map(x => x.message)).to.include(DiagnosticMessages.missingRightSquareBracketAfterDimIdentifier().message);
+        expectDiagnostics(parser, [
+            DiagnosticMessages.unmatchedLeftToken('[', 'dim identifier').message
+        ]);
     });
 
     it('flags missing expression(s)', () => {
diff --git a/src/parser/tests/statement/Enum.spec.ts b/src/parser/tests/statement/Enum.spec.ts
index b6e458ea..861ccd16 100644
--- a/src/parser/tests/statement/Enum.spec.ts
+++ b/src/parser/tests/statement/Enum.spec.ts
@@ -439,10 +439,10 @@ describe('EnumStatement', () => {
             `);
             program.validate();
             expectDiagnostics(program, [{
-                ...DiagnosticMessages.unknownEnumValue('DOWN', 'Direction'),
+                ...DiagnosticMessages.cannotFindName('DOWN', 'Direction.DOWN', 'Direction', 'enum'),
                 location: { range: util.createRange(7, 36, 7, 40) }
             }, {
-                ...DiagnosticMessages.unknownEnumValue('down', 'Direction'),
+                ...DiagnosticMessages.cannotFindName('down', 'Direction.down', 'Direction', 'enum'),
                 location: { range: util.createRange(8, 36, 8, 40) }
             }]);
         });
@@ -463,10 +463,10 @@ describe('EnumStatement', () => {
             `);
             program.validate();
             expectDiagnostics(program, [{
-                ...DiagnosticMessages.unknownEnumValue('DOWN', 'Enums.Direction'),
+                ...DiagnosticMessages.cannotFindName('DOWN', 'Enums.Direction.DOWN', 'Enums.Direction', 'enum'),
                 location: { range: util.createRange(2, 42, 2, 46) }
             }, {
-                ...DiagnosticMessages.unknownEnumValue('down', 'Enums.Direction'),
+                ...DiagnosticMessages.cannotFindName('down', 'Enums.Direction.down', 'Enums.Direction', 'enum'),
                 location: { range: util.createRange(3, 42, 3, 46) }
             }]);
         });
diff --git a/src/parser/tests/statement/Increment.spec.ts b/src/parser/tests/statement/Increment.spec.ts
index 7dae7df1..6ff119e1 100644
--- a/src/parser/tests/statement/Increment.spec.ts
+++ b/src/parser/tests/statement/Increment.spec.ts
@@ -6,6 +6,7 @@ import { EOF, identifier, token } from '../Parser.spec';
 import { Range } from 'vscode-languageserver';
 import { DiagnosticMessages } from '../../../DiagnosticMessages';
 import util from '../../../util';
+import { expectDiagnostics } from '../../../testHelpers.spec';
 
 describe('parser postfix unary expressions', () => {
     it('parses postfix \'++\' for variables', () => {
@@ -56,11 +57,7 @@ describe('parser postfix unary expressions', () => {
             token(TokenKind.PlusPlus, '++'),
             EOF
         ]);
-
-        expect(diagnostics).to.be.lengthOf(1);
-        expect(diagnostics[0]).deep.include({
-            message: 'Consecutive increment/decrement operators are not allowed'
-        });
+        expectDiagnostics(diagnostics, [DiagnosticMessages.unexpectedOperator().code]);
     });
 
     it('disallows postfix \'--\' for function call results', () => {
@@ -74,7 +71,7 @@ describe('parser postfix unary expressions', () => {
 
         expect(diagnostics).to.be.lengthOf(1);
         expect(diagnostics[0]).to.deep.include({
-            ...DiagnosticMessages.incrementDecrementOperatorsAreNotAllowedAsResultOfFunctionCall()
+            ...DiagnosticMessages.unexpectedOperator()
         });
     });
 
diff --git a/src/parser/tests/statement/TryCatch.spec.ts b/src/parser/tests/statement/TryCatch.spec.ts
index 33a4eaba..168a8f78 100644
--- a/src/parser/tests/statement/TryCatch.spec.ts
+++ b/src/parser/tests/statement/TryCatch.spec.ts
@@ -159,7 +159,7 @@ describe('parser try/catch', () => {
         `);
         expectDiagnosticsIncludes(parser, [
             DiagnosticMessages.expectedCatchBlockInTryCatch().message,
-            DiagnosticMessages.expectedEndTryToTerminateTryCatch().message
+            DiagnosticMessages.expectedTerminator('end try', 'try-catch', 'statement').message
         ]);
     });
 });
diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts
index 1d65d728..175ab6f7 100644
--- a/src/testHelpers.spec.ts
+++ b/src/testHelpers.spec.ts
@@ -1,7 +1,7 @@
 import type { BsDiagnostic } from './interfaces';
 import * as assert from 'assert';
 import chalk from 'chalk';
-import type { CodeDescription, CompletionItem, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, integer, Location } from 'vscode-languageserver';
+import type { CodeDescription, CompletionItem, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location } from 'vscode-languageserver';
 import { createSandbox } from 'sinon';
 import { expect } from './chai-config.spec';
 import type { CodeActionShorthand } from './CodeActionUtil';
@@ -62,7 +62,7 @@ function cloneObject<TOriginal, TTemplate>(original: TOriginal, template: TTempl
 interface PartialDiagnostic {
     location?: Partial<Location>;
     severity?: DiagnosticSeverity;
-    code?: integer | string;
+    code?: number | string;
     codeDescription?: Partial<CodeDescription>;
     source?: string;
     message?: string;
@@ -70,6 +70,7 @@ interface PartialDiagnostic {
     relatedInformation?: Partial<DiagnosticRelatedInformation>[];
     data?: unknown;
     file?: Partial<BscFile>;
+    legacyCode?: number | string;
 }
 
 /**
@@ -79,7 +80,7 @@ function cloneDiagnostic(actualDiagnosticInput: BsDiagnostic, expectedDiagnostic
     const actualDiagnostic = cloneObject(
         actualDiagnosticInput,
         expectedDiagnostic,
-        ['message', 'code', 'severity', 'relatedInformation']
+        ['message', 'code', 'severity', 'relatedInformation', 'legacyCode']
     );
     //clone Location if available
     if (expectedDiagnostic?.location) {
@@ -114,9 +115,14 @@ export function expectDiagnostics(arg: DiagnosticCollection, expected: Array<Par
         expected.map(x => {
             let result = x;
             if (typeof x === 'string') {
-                result = { message: x };
+                if (!x.includes(' ') && x.toLowerCase() === x) {
+                    // it's all lowercase and there's no spaces, so probably a code
+                    result = { code: x };
+                } else {
+                    result = { message: x };
+                }
             } else if (typeof x === 'number') {
-                result = { code: x };
+                result = { legacyCode: x };
             }
             return result as unknown as BsDiagnostic;
         })
@@ -128,7 +134,8 @@ export function expectDiagnostics(arg: DiagnosticCollection, expected: Array<Par
         const actualDiagnostic = cloneDiagnostic(actualDiagnostics[i], expectedDiagnostic);
         actual.push(actualDiagnostic as any);
     }
-    expect(actual).to.eql(expectedDiagnostics);
+    const sortedActual = sortDiagnostics(actual);
+    expect(sortedActual).to.eql(expectedDiagnostics);
 }
 
 /**
@@ -143,9 +150,14 @@ export function expectDiagnosticsIncludes(arg: DiagnosticCollection, expected: P
         actualExpected.map(x => {
             let result = x;
             if (typeof x === 'string') {
-                result = { message: x };
+                if (!x.includes(' ') && x.toLowerCase() === x) {
+                    // it's all lowercase and there's no spaces, so probably a code
+                    result = { code: x };
+                } else {
+                    result = { message: x };
+                }
             } else if (typeof x === 'number') {
-                result = { code: x };
+                result = { legacyCode: x };
             }
             return result as unknown as BsDiagnostic;
         });