diff --git a/bsconfig.schema.json b/bsconfig.schema.json index af9668980..311fe0eba 100644 --- a/bsconfig.schema.json +++ b/bsconfig.schema.json @@ -139,6 +139,16 @@ ] } }, + "severityOverride": { + "description": "A map of error codes with their severity level override (error|warn|info)", + "type": "object", + "patternProperties": { + ".{1,}": { + "type": ["number", "string"], + "enum": ["error", "warn", "info", "hint"] + } + } + }, "emitFullPaths": { "description": "Emit full paths to files when printing diagnostics to the console.", "type": "boolean", diff --git a/src/BsConfig.ts b/src/BsConfig.ts index 51c6ba995..af2f09089 100644 --- a/src/BsConfig.ts +++ b/src/BsConfig.ts @@ -106,6 +106,11 @@ export interface BsConfig { */ ignoreErrorCodes?: (number | string)[]; + /** + * A map of error codes with their severity level override (error|warn|info) + */ + severityOverride?: Record; + /** * Emit full paths to files when printing diagnostics to the console. Defaults to false */ diff --git a/src/DiagnosticSeverityAdjuster.spec.ts b/src/DiagnosticSeverityAdjuster.spec.ts new file mode 100644 index 000000000..f81d84fdd --- /dev/null +++ b/src/DiagnosticSeverityAdjuster.spec.ts @@ -0,0 +1,53 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; +import { expect } from './chai-config.spec'; + +import { DiagnosticSeverityAdjuster } from './DiagnosticSeverityAdjuster'; +import type { BsDiagnostic } from './interfaces'; + +describe('DiagnosticSeverityAdjuster', () => { + const adjuster = new DiagnosticSeverityAdjuster(); + + it('supports empty map', () => { + const actual = adjuster.createSeverityMap({}); + expect(Array.from(actual.keys()).length === 0); + }); + + it('maps strings to enums', () => { + const actual = adjuster.createSeverityMap({ + 'a': 'error', + 'b': 'warn', + 'c': 'info', + 1001: 'hint', + // @ts-expect-error using invalid key + 'e': 'foo', + // @ts-expect-error using invalid key + 'f': 42 + }); + expect(actual.get('a')).to.equal(DiagnosticSeverity.Error); + expect(actual.get('b')).to.equal(DiagnosticSeverity.Warning); + expect(actual.get('c')).to.equal(DiagnosticSeverity.Information); + expect(actual.get('1001')).to.equal(DiagnosticSeverity.Hint); + expect(actual.get('e')).to.equal(undefined); + expect(actual.get('f')).to.equal(undefined); + }); + + it('adjusts severity', () => { + const diagnostics = [ + { + code: 'BSLINT1001', + severity: DiagnosticSeverity.Error + } as BsDiagnostic, { + code: 1001, + severity: DiagnosticSeverity.Error + } as BsDiagnostic + ]; + adjuster.adjust({ + severityOverride: { + 'BSLINT1001': 'warn', + 1001: 'info' + } + }, diagnostics); + expect(diagnostics[0].severity).to.equal(DiagnosticSeverity.Warning); + expect(diagnostics[1].severity).to.equal(DiagnosticSeverity.Information); + }); +}); diff --git a/src/DiagnosticSeverityAdjuster.ts b/src/DiagnosticSeverityAdjuster.ts new file mode 100644 index 000000000..1c479732c --- /dev/null +++ b/src/DiagnosticSeverityAdjuster.ts @@ -0,0 +1,38 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; +import type { BsConfig } from './BsConfig'; +import type { BsDiagnostic } from './interfaces'; + +export class DiagnosticSeverityAdjuster { + public adjust(options: BsConfig, diagnostics: BsDiagnostic[]): void { + const map = this.createSeverityMap(options.severityOverride); + + diagnostics.forEach(diagnostic => { + const code = String(diagnostic.code); + if (map.has(code)) { + diagnostic.severity = map.get(code); + } + }); + } + + public createSeverityMap(severityOverride: BsConfig['severityOverride']): Map { + const map = new Map(); + Object.keys(severityOverride).forEach(key => { + const value = severityOverride[key]; + switch (value) { + case 'error': + map.set(key, DiagnosticSeverity.Error); + break; + case 'warn': + map.set(key, DiagnosticSeverity.Warning); + break; + case 'info': + map.set(key, DiagnosticSeverity.Information); + break; + case 'hint': + map.set(key, DiagnosticSeverity.Hint); + break; + } + }); + return map; + } +} diff --git a/src/Program.ts b/src/Program.ts index 4abb48b63..fbeaa0e62 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -28,6 +28,7 @@ import { rokuDeploy } from 'roku-deploy'; import type { Statement } from './parser/AstNode'; import { CallExpressionInfo } from './bscPlugin/CallExpressionInfo'; import { SignatureHelpUtil } from './bscPlugin/SignatureHelpUtil'; +import { DiagnosticSeverityAdjuster } from './DiagnosticSeverityAdjuster'; const startOfSourcePkgPath = `source${path.sep}`; const bslibNonAliasedRokuModulesPkgPath = s`source/roku_modules/rokucommunity_bslib/bslib.brs`; @@ -102,6 +103,8 @@ export class Program { private diagnosticFilterer = new DiagnosticFilterer(); + private diagnosticAdjuster = new DiagnosticSeverityAdjuster(); + /** * A scope that contains all built-in global functions. * All scopes should directly or indirectly inherit from this scope @@ -288,6 +291,10 @@ export class Program { return finalDiagnostics; }); + this.logger.time(LogLevel.debug, ['adjust diagnostics severity'], () => { + this.diagnosticAdjuster.adjust(this.options, diagnostics); + }); + this.logger.info(`diagnostic counts: total=${chalk.yellow(diagnostics.length.toString())}, after filter=${chalk.yellow(filteredDiagnostics.length.toString())}`); return filteredDiagnostics; }); diff --git a/src/util.ts b/src/util.ts index d1676e8f6..e2ed197b7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -336,6 +336,7 @@ export class Util { config.retainStagingFolder = config.retainStagingDir; config.copyToStaging = config.copyToStaging === false ? false : true; config.ignoreErrorCodes = config.ignoreErrorCodes ?? []; + config.severityOverride = config.severityOverride ?? {}; config.diagnosticFilters = config.diagnosticFilters ?? []; config.plugins = config.plugins ?? []; config.autoImportComponentScript = config.autoImportComponentScript === true ? true : false;