diff --git a/bsconfig.schema.json b/bsconfig.schema.json index bdd05d56b..167ebc700 100644 --- a/bsconfig.schema.json +++ b/bsconfig.schema.json @@ -304,6 +304,35 @@ "type": "string", "description": "A file path relative to rootDir, an absolute path, or a file glob pointing to file(s) you wish to filter diagnostics from." }, + "files": { + "description": "A file glob or list of file globs/src/dest descriptors pointing to file(s) you wish to filter diagnostics from.", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "dest": { + "type": "string" + } + } + } + ] + } + } + ] + }, "codes": { "type": "array", "description": "A list of codes of diagnostics that should be filtered out from the files matched in 'src'`. If omitted, all error codes are used", @@ -318,6 +347,16 @@ } ] } + }, + "minSeverity": { + "type": "string", + "description": "The minimum diagnostic severity to keep (error|warn|info|hint). Diagnostics with a lower severity will be filtered out.", + "enum": [ + "error", + "warn", + "info", + "hint" + ] } } } diff --git a/docs/bsconfig.md b/docs/bsconfig.md index f8cfbe8e2..c6e13039d 100644 --- a/docs/bsconfig.md +++ b/docs/bsconfig.md @@ -123,7 +123,7 @@ If present, overrides the current working directory when invoking `bsc`. Default ## `diagnosticFilters` -Type: `Array; codes?: Array}` +Type: `Array; codes?: Array; minSeverity?: 'error' | 'warn' | 'info' | 'hint'}>` A list of filters used to hide diagnostics. @@ -137,7 +137,7 @@ A list of filters used to hide diagnostics. ] ``` -- 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, +- An object can also be provided to filter specific diagnostic codes and/or set a minimum severity threshold (`minSeverity`) for a file pattern. If no `files` property is included, any diagnostic that matches the values in `codes` and/or has a severity lower than `minSeverity` will be suppressed globally. If a `string` is 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": [{ @@ -154,11 +154,41 @@ A list of filters used to hide diagnostics. "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 }] ``` +- An object with `minSeverity` can be used to filter out diagnostics below a minimum severity threshold (`"error"`, `"warn"`, `"info"`, `"hint"`), either globally (when `files` is omitted) or for specific `files` patterns: + + ```jsonc + "diagnosticFilters": [ + // filter out hint and info diagnostics in components (keep warn and error) + { + "files": "components/**/*", + "minSeverity": "warn" + }, + // filter out warnings, info, and hint diagnostics in vendor files (keep only errors) + { + "files": "vendor/**/*", + "minSeverity": "error" + } + ] + ``` + +- When an object specifies both `codes` and `minSeverity`, a diagnostic is suppressed only when it matches **both** a code in `codes` **and** has a severity lower than `minSeverity` (logical AND). To suppress diagnostics that match either condition (logical OR), use separate filter objects: + + ```jsonc + "diagnosticFilters": [ + // suppress cannot-find-name in vendor files when its severity is below error + { + "files": "vendor/**/*", + "codes": ["cannot-find-name"], + "minSeverity": "error" + } + ] + ``` + - 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`. @@ -171,7 +201,7 @@ If a child bsconfig extends from a parent bsconfig, and both bsconfigs specify ` ### Negative patterns in `diagnosticFilters` -A negative pattern can be used to un-ignore some files or codes which were previously ignored. For example, +A negative pattern can be used to un-ignore some files, codes, or severities which were previously ignored. For example, ```jsonc "diagnosticFilters": [ @@ -180,12 +210,13 @@ A negative pattern can be used to un-ignore some files or codes which were previ ] ``` -A specific error code can be unignored in multiple places by using a pattern which matches everything under `rootDir`. For example, +A specific error code or severity threshold can be unignored in multiple places by using a pattern which matches everything under `rootDir`. For example, ```jsonc "diagnosticFilters": [ - { "files": "vendor/**/*" }, //ignore all errors from vendor libraries - { "files": "!*/**/*", "codes": ["name-collision"] } //EXCEPT do show this particular code everywhere + { "files": "vendor/**/*" }, //ignore all diagnostics from vendor libraries + { "files": "!*/**/*", "codes": ["name-collision"] }, //EXCEPT do show this particular code everywhere + { "files": "!vendor/**/*", "minSeverity": "error" } //AND still show errors from vendor libraries ] ``` diff --git a/src/BsConfig.ts b/src/BsConfig.ts index 8e004187d..2ef0b267d 100644 --- a/src/BsConfig.ts +++ b/src/BsConfig.ts @@ -1,5 +1,8 @@ +import type { DiagnosticSeverity } from 'vscode-languageserver-protocol'; import type { LogLevel } from './logging'; +export type BsDiagnosticSeverity = 'error' | 'warn' | 'info' | 'hint' | DiagnosticSeverity; + export interface BsConfig { /** * The inheritance tree for all parent configs used to generate this config. Do not set this, it is computed. @@ -118,7 +121,15 @@ export interface BsConfig { /** * A list of filters used to exclude diagnostics from the output */ - diagnosticFilters?: Array; codes?: Array }>; + diagnosticFilters?: Array< + | string + | number + | { + files?: string | Array; + codes?: Array; + minSeverity?: BsDiagnosticSeverity; + } + >; /** * Use the deprecated diagnosticFilters format from v0. This is useful for backwards compatibility. diff --git a/src/DiagnosticFilterer.spec.ts b/src/DiagnosticFilterer.spec.ts index 09a90f44a..da6f730b2 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 { DiagnosticSeverity } from 'vscode-languageserver-protocol'; import { Program } from './Program'; import util, { standardizePath as s } from './util'; import { createSandbox } from 'sinon'; @@ -794,14 +795,234 @@ describe('DiagnosticFilterer', () => { }); }); + describe('filtering by minSeverity', () => { + it('normalizes global and file-scoped minSeverity filters', () => { + expect(filterer.getDiagnosticFilters({ + diagnosticFilters: [ + { minSeverity: 'warn' }, + { files: 'lib/**/*.brs', minSeverity: 'error' }, + { files: '!lib/critical/**/*.brs', minSeverity: 'warn' }, + { files: 'source/**/*.brs', codes: [1001], minSeverity: 'error' } + ] + })).to.eql([ + { minSeverity: 'warn', isNegative: false }, + { src: 'lib/**/*.brs', minSeverity: 'error', isNegative: false }, + { src: 'lib/critical/**/*.brs', minSeverity: 'warn', isNegative: true }, + { src: 'source/**/*.brs', codes: [1001], minSeverity: 'error', isNegative: false } + ]); + }); + + it('normalizes minSeverity filters in v0 compatibility mode', () => { + expect(filterer.getDiagnosticFilters({ + diagnosticFiltersV0Compatibility: true, + diagnosticFilters: [ + { minSeverity: 'warn' }, + { src: 'lib/**/*.brs', minSeverity: 'error' }, + { src: '!lib/critical/**/*.brs', codes: [100], minSeverity: 'warn' } + ] as any + })).to.eql([ + { minSeverity: 'warn', isNegative: false }, + { src: 'lib/**/*.brs', minSeverity: 'error', isNegative: false }, + { src: 'lib/critical/**/*.brs', codes: [100], minSeverity: 'warn', isNegative: true } + ]); + }); + + it('filters diagnostics globally by minSeverity string and DiagnosticSeverity enum', () => { + const diagnostics = [ + getDiagnostic(1, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Error), + getDiagnostic(2, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Warning), + getDiagnostic(3, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Information), + getDiagnostic(4, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Hint) + ]; + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { minSeverity: 'warn' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2]); + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { minSeverity: 'error' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1]); + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { minSeverity: 'info' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2, 3]); + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { minSeverity: DiagnosticSeverity.Warning } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2]); + }); + + it('filters diagnostics by files glob and minSeverity', () => { + const diagnostics = [ + getDiagnostic(1, `${rootDir}/components/comp.brs`, undefined, DiagnosticSeverity.Error), + getDiagnostic(2, `${rootDir}/components/comp.brs`, undefined, DiagnosticSeverity.Warning), + getDiagnostic(3, `${rootDir}/components/comp.brs`, undefined, DiagnosticSeverity.Information), + getDiagnostic(4, `${rootDir}/components/comp.brs`, undefined, DiagnosticSeverity.Hint), + getDiagnostic(5, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Hint) + ]; + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { files: 'components/**/*', minSeverity: 'warn' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2, 5]); + }); + + it('filters diagnostics by dest path and minSeverity', () => { + const program = new Program({ rootDir: rootDir }); + program.setFile('source/common.brs', ''); + program.setFile({ src: `${rootDir}/source/utils.brs`, dest: 'source/vendor/utils.brs' }, ''); + + const diagnostics = [ + getDiagnostic('err1', `${rootDir}/source/utils.brs`, `${rootDir}/source/vendor/utils.brs`, DiagnosticSeverity.Error), + getDiagnostic('warn1', `${rootDir}/source/utils.brs`, `${rootDir}/source/vendor/utils.brs`, DiagnosticSeverity.Warning), + getDiagnostic('info1', `${rootDir}/source/utils.brs`, `${rootDir}/source/vendor/utils.brs`, DiagnosticSeverity.Information), + getDiagnostic('warn2', `${rootDir}/source/common.brs`, undefined, DiagnosticSeverity.Warning) + ]; + + const result = filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { + files: [{ dest: 'source/vendor/**/*.*' }], + minSeverity: 'error' + } + ] + }, diagnostics, program); + + expect(result.map(x => x.code)).to.eql(['err1', 'warn2']); + }); + + it('combines codes and minSeverity in the same filter object using logical AND', () => { + const diagnostics = [ + getDiagnostic(100, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Warning), // matches code and is below error -> suppress + getDiagnostic(100, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Error), // matches code but meets minSeverity -> keep + getDiagnostic(200, `${rootDir}/source/main.brs`, undefined, DiagnosticSeverity.Warning) // below minSeverity but different code -> keep + ]; + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { files: 'source/**/*.brs', codes: [100], minSeverity: 'error' } + ] + }, diagnostics).map(x => `${x.code}:${x.severity}`) + ).to.eql([ + `100:${DiagnosticSeverity.Error}`, + `200:${DiagnosticSeverity.Warning}` + ]); + }); + + it('supports negative globs with minSeverity to un-ignore diagnostics meeting the threshold', () => { + const diagnostics = [ + getDiagnostic(1, `${rootDir}/lib/vendor.brs`, undefined, DiagnosticSeverity.Error), + getDiagnostic(2, `${rootDir}/lib/vendor.brs`, undefined, DiagnosticSeverity.Warning), + getDiagnostic(3, `${rootDir}/lib/vendor.brs`, undefined, DiagnosticSeverity.Information) + ]; + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { files: 'lib/**/*.brs' }, + { files: '!lib/**/*.brs', minSeverity: 'warn' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2]); + }); + + it('does not mark a file as completely filtered when the filter specifies minSeverity', () => { + filterer.options = { + rootDir: rootDir, + diagnosticFilters: [ + { files: 'lib/**/*.brs', minSeverity: 'error' } + ] + }; + expect( + filterer.isFileCompletelyFiltered({ + srcPath: `${rootDir}/lib/some/file.brs`, + destPath: 'source/some/file.brs' + }) + ).to.be.false; + }); + + it('treats diagnostics with no severity as Error by default', () => { + const diagnostics = [ + getDiagnostic(1, `${rootDir}/lib/vendor.brs`), // no severity -> defaults to Error + getDiagnostic(2, `${rootDir}/lib/vendor.brs`, undefined, DiagnosticSeverity.Warning), + getDiagnostic(3, `${rootDir}/lib/vendor.brs`, undefined, DiagnosticSeverity.Information) + ]; + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { minSeverity: 'warn' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1, 2]); + + expect( + filterer.filter({ + rootDir: rootDir, + diagnosticFilters: [ + { files: 'lib/**/*.brs' }, + { files: '!lib/**/*.brs', minSeverity: 'error' } + ] + }, diagnostics).map(x => x.code) + ).to.eql([1]); + }); + + it('ignores invalid minSeverity values during filter normalization', () => { + expect( + filterer.getDiagnosticFilters({ + diagnosticFilters: [ + { minSeverity: 'foo' as any }, + { minSeverity: 999 as any }, + { codes: [100], minSeverity: 'foo' as any }, + { files: 'vendor/**/*', minSeverity: 'foo' as any } + ] + }) + ).to.eql([ + { codes: [100], isNegative: false }, + { src: 'vendor/**/*', isNegative: false } + ]); + }); + }); + }); -function getDiagnostic(code: number | string, srcPath: string, destPath?: string) { +function getDiagnostic(code: number | string, srcPath: string, destPath?: string, severity?: DiagnosticSeverity) { destPath = destPath ?? srcPath; return { location: { uri: util.pathToUri(s`${srcPath}`) }, - code: code + code: code, + ...(severity !== undefined ? { severity: severity } : {}) } as BsDiagnostic; } + diff --git a/src/DiagnosticFilterer.ts b/src/DiagnosticFilterer.ts index 4faa40643..162890175 100644 --- a/src/DiagnosticFilterer.ts +++ b/src/DiagnosticFilterer.ts @@ -1,7 +1,8 @@ import type { BsDiagnostic } from './interfaces'; +import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; import * as path from 'path'; import * as micromatch from 'micromatch'; -import type { BsConfig } from './BsConfig'; +import type { BsConfig, BsDiagnosticSeverity } from './BsConfig'; import util, { standardizePath as s } from './util'; import { URI } from 'vscode-uri'; import type { Program } from './Program'; @@ -15,6 +16,7 @@ interface NormalizedFilter { src?: string; dest?: string; codes?: (number | string)[]; + minSeverity?: BsDiagnosticSeverity; isNegative: boolean; } @@ -99,7 +101,7 @@ export class DiagnosticFilterer { let isMatch = false; //filter each matched file for (let filter of this.filters) { - if (filter.codes?.length > 0) { + if (filter.codes?.length > 0 || filter.minSeverity !== undefined) { continue; } let srcMatch = false; @@ -242,26 +244,77 @@ export class DiagnosticFilterer { //if the filter is not negative we're turning diagnostics off const isSuppressing = !filter.isNegative; const lowerFileUri = fileUri.toLowerCase(); - //if there is no code, set isSuppressed on every diagnostic in this file - if (!filter.codes) { + const hasCodes = Array.isArray(filter.codes); + const hasMinSeverity = filter.minSeverity !== undefined; + //if there is no code or minSeverity filter, set isSuppressed on every diagnostic in this file + if (!hasCodes && !hasMinSeverity) { this.byFile[lowerFileUri].forEach(diagnostic => { diagnostic.isSuppressed = isSuppressing; }); - //set isSuppressed for any diagnostics with matching codes + //set isSuppressed for any diagnostics with matching codes and/or minSeverity } else { let fileDiagnostics = this.byFile[lowerFileUri]; for (const diagnostic of fileDiagnostics) { - if (filter.codes.includes(diagnostic.diagnostic.code!) || - (diagnostic.diagnostic.legacyCode && - (filter.codes.includes(diagnostic.diagnostic.legacyCode) || - filter.codes.includes(diagnostic.diagnostic.legacyCode.toString())))) { + const codeMatches = hasCodes ? this.matchesCode(filter, diagnostic.diagnostic) : true; + const severityMatches = hasMinSeverity ? this.matchesSeverity(filter, diagnostic.diagnostic) : true; + if (codeMatches && severityMatches) { diagnostic.isSuppressed = isSuppressing; } } } } + private matchesCode(filter: NormalizedFilter, diagnostic: BsDiagnostic): boolean { + return !!( + filter.codes?.includes(diagnostic.code!) || + (diagnostic.legacyCode && + (filter.codes?.includes(diagnostic.legacyCode) || + filter.codes?.includes(diagnostic.legacyCode.toString()))) + ); + } + + private matchesSeverity(filter: NormalizedFilter, diagnostic: BsDiagnostic): boolean { + const diagnosticSeverity = this.toDiagnosticSeverity(diagnostic.severity) ?? DiagnosticSeverity.Error; + const minSeverity = this.toDiagnosticSeverity(filter.minSeverity); + if (minSeverity === undefined) { + return false; + } + if (filter.isNegative) { + return diagnosticSeverity <= minSeverity; + } + return diagnosticSeverity > minSeverity; + } + + private toDiagnosticSeverity(value: unknown): DiagnosticSeverity | undefined { + if (typeof value === 'number') { + if ( + value === DiagnosticSeverity.Error || + value === DiagnosticSeverity.Warning || + value === DiagnosticSeverity.Information || + value === DiagnosticSeverity.Hint + ) { + return value; + } + return undefined; + } + if (typeof value === 'string') { + switch (value.toLowerCase()) { + case 'error': + return DiagnosticSeverity.Error; + case 'warn': + case 'warning': + return DiagnosticSeverity.Warning; + case 'info': + case 'information': + return DiagnosticSeverity.Information; + case 'hint': + return DiagnosticSeverity.Hint; + } + } + return undefined; + } + public getDiagnosticFilters(config: BsConfig) { if (config.diagnosticFiltersV0Compatibility) { return this.getDiagnosticFiltersV0(config); @@ -295,12 +348,21 @@ export class DiagnosticFilterer { continue; } - //code-only filter - if ('codes' in filter && !('files' in filter) && Array.isArray(filter.codes)) { - result.push({ - codes: filter.codes, + const hasCodes = 'codes' in filter && Array.isArray(filter.codes); + const hasMinSeverity = 'minSeverity' in filter && this.toDiagnosticSeverity(filter.minSeverity) !== undefined; + + //code/minSeverity-only filter + if (!('files' in filter) && (hasCodes || hasMinSeverity)) { + const normalized: NormalizedFilter = { isNegative: false - }); + }; + if (hasCodes) { + normalized.codes = filter.codes; + } + if (hasMinSeverity) { + normalized.minSeverity = filter.minSeverity; + } + result.push(normalized); continue; } @@ -373,12 +435,21 @@ export class DiagnosticFilterer { continue; } - //code-only filter - if ('codes' in filter && !('src' in filter) && Array.isArray(filter.codes)) { - result.push({ - codes: filter.codes, + const hasCodes = 'codes' in filter && Array.isArray(filter.codes); + const hasMinSeverity = 'minSeverity' in filter && this.toDiagnosticSeverity(filter.minSeverity) !== undefined; + + //code/minSeverity-only filter + if (!('src' in filter) && (hasCodes || hasMinSeverity)) { + const normalized: NormalizedFilter = { isNegative: false - }); + }; + if (hasCodes) { + normalized.codes = filter.codes; + } + if (hasMinSeverity) { + normalized.minSeverity = filter.minSeverity; + } + result.push(normalized); continue; } @@ -386,56 +457,41 @@ export class DiagnosticFilterer { const isNegative = filter.src.startsWith('!'); const trimmedFilter = isNegative ? filter.src.slice(1) : filter.src; + const normalized: NormalizedFilter = { + src: trimmedFilter, + isNegative: isNegative + }; if ('codes' in filter) { - result.push({ - src: trimmedFilter, - codes: filter.codes, - isNegative: isNegative - }); - } else { - result.push({ - src: trimmedFilter, - isNegative: isNegative - }); + normalized.codes = filter.codes; + } + if (hasMinSeverity) { + normalized.minSeverity = filter.minSeverity; } + result.push(normalized); } } return result; } - private getNormalizedFilter(fileGlob: string, filter: { files: string } | { codes?: (number | string)[] }, locationKey: 'src' | 'dest' = 'src'): NormalizedFilter { + private getNormalizedFilter( + fileGlob: string, + filter: { files?: any; codes?: (number | string)[]; minSeverity?: BsDiagnosticSeverity }, + 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 - }; - } + const normalized: NormalizedFilter = { + [locationKey]: trimmedFilter, + isNegative: isNegative + }; + if ('codes' in filter && Array.isArray(filter.codes)) { + normalized.codes = filter.codes; } - + if ('minSeverity' in filter && this.toDiagnosticSeverity(filter.minSeverity) !== undefined) { + normalized.minSeverity = filter.minSeverity; + } + return normalized; } /** diff --git a/src/DiagnosticManager.spec.ts b/src/DiagnosticManager.spec.ts index 61e32cbd3..305b77a51 100644 --- a/src/DiagnosticManager.spec.ts +++ b/src/DiagnosticManager.spec.ts @@ -6,6 +6,7 @@ import { expectDiagnostics, expectZeroDiagnostics } from './testHelpers.spec'; import util from './util'; import { expect } from 'chai'; import { DiagnosticMessages } from './DiagnosticMessages'; +import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; describe('DiagnosticManager', () => { @@ -396,4 +397,40 @@ describe('DiagnosticManager', () => { expect(program.diagnostics.canSkipScopeValidationForFile(file)).to.be.false; }); }); + + describe('getDiagnostics with diagnosticSeverityOverrides and minSeverity', () => { + it('applies diagnosticSeverityOverrides before filtering by minSeverity', () => { + program = new Program({ + diagnosticSeverityOverrides: { + 1001: 'hint', + 1002: 'warn' + }, + diagnosticFilters: [ + { minSeverity: 'warn' } + ] + }); + const location = { uri: 'source/main.brs', range: util.createRange(1, 2, 3, 4) }; + program.diagnostics.register([ + { + message: 'warn overridden to hint should be hidden', + location: location, + code: 1001, + severity: DiagnosticSeverity.Warning + }, + { + message: 'info overridden to warn should be kept', + location: location, + code: 1002, + severity: DiagnosticSeverity.Information + } + ]); + + expectDiagnostics(program.getDiagnostics(), [ + { + code: 1002, + severity: DiagnosticSeverity.Warning + } + ]); + }); + }); }); diff --git a/src/DiagnosticManager.ts b/src/DiagnosticManager.ts index c727d5d88..beca63ef5 100644 --- a/src/DiagnosticManager.ts +++ b/src/DiagnosticManager.ts @@ -164,14 +164,14 @@ export class DiagnosticManager { public getDiagnostics(): BsDiagnostic[] { const doDiagnosticsGathering = () => { const diagnostics = this.getNonSuppressedDiagnostics(); + this.logger?.time(LogLevel.debug, ['adjust diagnostics severity'], () => { + this.diagnosticAdjuster?.adjust(this.options ?? {}, diagnostics); + }); + const filteredDiagnostics = this.logger?.time(LogLevel.debug, ['filter diagnostics'], () => { return this.filterDiagnostics(diagnostics); }) ?? this.filterDiagnostics(diagnostics); - this.logger?.time(LogLevel.debug, ['adjust diagnostics severity'], () => { - this.diagnosticAdjuster?.adjust(this.options ?? {}, filteredDiagnostics); - }); - this.logger?.info(`diagnostic counts: total=${chalk.yellow(diagnostics.length.toString())}, after filter=${chalk.yellow(filteredDiagnostics.length.toString())}`); return filteredDiagnostics;