diff --git a/$shared/settings.ts b/$shared/settings.ts index fcb2b169..19ba88d0 100644 --- a/$shared/settings.ts +++ b/$shared/settings.ts @@ -176,6 +176,11 @@ export type ConfigurationSettings = { codeActionOnSave: CodeActionsOnSaveSettings; format: boolean; quiet: boolean; + bulkSuppression: { + enable: boolean; + suppressionsLocation?: string; + severity?: 'error' | 'warn' | 'info' | 'hint'; + }; onIgnoredFiles: ESLintSeverity; options: ESLintOptions | undefined; rulesCustomizations: RuleCustomization[]; diff --git a/client/src/client.ts b/client/src/client.ts index 9cfe1d23..8b6e1138 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -703,6 +703,7 @@ export namespace ESLintClient { }, format: false, quiet: config.get('quiet', false), + bulkSuppression: config.get('bulkSuppression', { enable: false }), onIgnoredFiles: ESLintSeverity.from(config.get('onIgnoredFiles', ESLintSeverity.off)), options: config.get('options', {}), rulesCustomizations: getRuleCustomizations(config, resource), diff --git a/package.json b/package.json index 6655d45a..ecb26cf2 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,29 @@ "default": false, "description": "Turns on quiet mode, which ignores warnings and info diagnostics." }, + "eslint.bulkSuppression": { + "type": "object", + "scope": "resource", + "description": "Controls whether bulk-suppressed violations (from eslint-suppressions.json) are shown as hint diagnostics.", + "properties": { + "enable": { + "type": "boolean", + "default": false, + "description": "Show bulk-suppressed violations as hint (grey) diagnostics." + }, + "suppressionsLocation": { + "type": "string", + "description": "Path to the suppressions file relative to the workspace. Defaults to eslint-suppressions.json." + }, + "severity": { + "type": "string", + "enum": ["error", "warn", "info", "hint"], + "default": "info", + "description": "Diagnostic severity for bulk-suppressed violations. Defaults to 'info' (blue underline)." + } + }, + "default": { "enable": false, "severity": "info" } + }, "eslint.onIgnoredFiles": { "scope": "resource", "type": "string", diff --git a/server/src/eslint.ts b/server/src/eslint.ts index 84ae2d2b..c0e45e03 100644 --- a/server/src/eslint.ts +++ b/server/src/eslint.ts @@ -81,11 +81,16 @@ type ESLintProblem = { suggestions?: ESLintSuggestionResult[]; }; +type SuppressedESLintProblem = ESLintProblem & { + suppressions: Array<{ kind: string; justification: string }>; +}; + type ESLintDocumentReport = { filePath: string; errorCount: number; warningCount: number; messages: ESLintProblem[]; + suppressedMessages?: SuppressedESLintProblem[]; output?: string; }; @@ -115,6 +120,8 @@ export type ESLintClassOptions = { fix?: boolean; overrideConfig?: ConfigData; overrideConfigFile?: string | null; + applySuppressions?: boolean; + suppressionsLocation?: string; }; export type RuleMetaData = { @@ -1195,6 +1202,15 @@ export namespace ESLint { } } + function bulkSeverity(severity?: 'error' | 'warn' | 'info' | 'hint'): DiagnosticSeverity { + switch (severity) { + case 'error': return DiagnosticSeverity.Error; + case 'warn': return DiagnosticSeverity.Warning; + case 'hint': return DiagnosticSeverity.Hint; + default: return DiagnosticSeverity.Information; + } + } + const validFixTypes = new Set(['problem', 'suggestion', 'layout', 'directive']); export async function validate(document: TextDocument, settings: TextDocumentSettings & { library: ESLintModule }): Promise { const newOptions: CLIOptions = Object.assign(Object.create(null), settings.options); @@ -1215,6 +1231,15 @@ export namespace ESLint { const uri = document.uri; const file = getFilePath(document, settings); + const suppressionOptions: ESLintClassOptions = settings.bulkSuppression?.enable + ? { + applySuppressions: true, + ...(settings.bulkSuppression.suppressionsLocation + ? { suppressionsLocation: settings.bulkSuppression.suppressionsLocation } + : {}), + } + : {}; + return withClass(async (eslintClass) => { CodeActions.remove(uri); const reportResults: ESLintDocumentReport[] = await eslintClass.lintText(content, { filePath: file, warnIgnored: settings.onIgnoredFiles !== ESLintSeverity.off }); @@ -1244,9 +1269,22 @@ export namespace ESLint { } }); } + // Bulk-suppressed diagnostics intentionally bypass `quiet` mode: they are opt-in + // and already represent a deliberate visibility decision by the user. + if (settings.bulkSuppression?.enable && docReport.suppressedMessages && Array.isArray(docReport.suppressedMessages)) { + for (const problem of docReport.suppressedMessages) { + if (problem && problem.suppressions?.some(s => s.kind === 'file')) { + const [diagnostic, override] = Diagnostics.create(settings, problem, document); + if (override !== RuleSeverity.off) { + diagnostic.severity = bulkSeverity(settings.bulkSuppression.severity); + diagnostics.push(diagnostic); + } + } + } + } } return diagnostics; - }, settings); + }, settings, suppressionOptions); } function trace(message: string, verbose?: string): void {