Skip to content

Commit c441922

Browse files
Simon Heathercursoragent
authored andcommitted
Add yaml-lint-disable comment to suppress diagnostics per-line
Add support for suppressing linter diagnostics on a per-line basis using a yaml-lint-disable comment placed on the line immediately before the one producing the diagnostic. The comment supports three forms: suppress all diagnostics, suppress by message substring, or suppress by multiple comma-separated substrings (case-insensitive). Implemented via LSP client middleware that intercepts diagnostics from the yaml-language-server and filters out suppressed ones before they reach VS Code. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 973a227 commit c441922

File tree

4 files changed

+440
-0
lines changed

4 files changed

+440
-0
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,33 @@ The following settings are supported:
6969
- `yaml.keyOrdering` : Enforces alphabetical ordering of keys in mappings when set to `true`. Default is `false`
7070
- `yaml.extension.recommendations` : Enable extension recommendations for YAML files. Default is `true`
7171

72+
## Suppressing diagnostics
73+
74+
You can suppress specific validation warnings on a per-line basis by adding a `# yaml-lint-disable` comment on the line immediately before the one producing the diagnostic.
75+
76+
### Suppress all diagnostics on a line
77+
78+
```yaml
79+
# yaml-lint-disable
80+
version: 123
81+
```
82+
83+
### Suppress only specific diagnostics
84+
85+
Provide one or more message substrings (comma-separated, case-insensitive). Only diagnostics whose message contains a matching substring will be suppressed; the rest are kept.
86+
87+
```yaml
88+
# yaml-lint-disable Incorrect type
89+
version: 123
90+
```
91+
92+
```yaml
93+
# yaml-lint-disable Incorrect type, not accepted
94+
version: 123
95+
```
96+
97+
The substrings are matched against the diagnostic messages shown in the VS Code **Problems** panel.
98+
7299
## Adding custom tags
73100
74101
To use the custom tags in your YAML file, you need to first specify the custom tags in the setting of your code editor. For example, you can have the following custom tags:

src/diagnostic-filter.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
/**
8+
* Pattern that matches a `# yaml-lint-disable` comment.
9+
*
10+
* Usage in YAML files:
11+
* # yaml-lint-disable ← suppress ALL diagnostics on the next line
12+
* # yaml-lint-disable Incorrect type ← suppress diagnostics whose message contains "Incorrect type"
13+
* # yaml-lint-disable Incorrect type, not accepted ← suppress diagnostics matching any of the substrings
14+
*
15+
* Capture group 1 (optional) contains the comma-separated list of message
16+
* substrings to match against. If absent, all diagnostics are suppressed.
17+
*/
18+
export const YAML_LINT_DISABLE_PATTERN = /^\s*#\s*yaml-lint-disable\b(.*)$/;
19+
20+
/**
21+
* A callback that returns the text content of a given zero-based line number,
22+
* or `undefined` if the line does not exist.
23+
*/
24+
export type GetLineText = (line: number) => string | undefined;
25+
26+
/**
27+
* Parse the text after `yaml-lint-disable` into an array of trimmed,
28+
* lower-cased message substrings. Returns an empty array when no
29+
* specifiers are provided (meaning "suppress all").
30+
*/
31+
export function parseDisableSpecifiers(raw: string): string[] {
32+
const trimmed = raw.trim();
33+
if (!trimmed) {
34+
return [];
35+
}
36+
return trimmed
37+
.split(',')
38+
.map((s) => s.trim().toLowerCase())
39+
.filter((s) => s.length > 0);
40+
}
41+
42+
/**
43+
* Determine whether a diagnostic should be suppressed based on the
44+
* specifiers from a `# yaml-lint-disable` comment.
45+
*
46+
* @param specifiers - Parsed specifiers (empty means suppress all).
47+
* @param diagnosticMessage - The diagnostic's message text.
48+
* @returns `true` if the diagnostic should be suppressed.
49+
*/
50+
export function shouldSuppressDiagnostic(specifiers: string[], diagnosticMessage: string): boolean {
51+
if (specifiers.length === 0) {
52+
return true; // no specifiers → suppress everything
53+
}
54+
const lowerMessage = diagnosticMessage.toLowerCase();
55+
return specifiers.some((spec) => lowerMessage.includes(spec));
56+
}
57+
58+
/**
59+
* Filters an array of diagnostics, removing any whose starting line is
60+
* immediately preceded by a `# yaml-lint-disable` comment.
61+
*
62+
* When the comment includes one or more comma-separated message substrings,
63+
* only diagnostics whose message contains at least one of those substrings
64+
* (case-insensitive) are suppressed. Without specifiers, all diagnostics
65+
* on the next line are suppressed.
66+
*
67+
* @param diagnostics - The diagnostics to filter.
68+
* @param getStartLine - Extracts the zero-based starting line number from a diagnostic.
69+
* @param getMessage - Extracts the message string from a diagnostic.
70+
* @param getLineText - Returns the text of a document line by its zero-based index,
71+
* or `undefined` if the line is out of range.
72+
* @returns A new array containing only the diagnostics that are not suppressed.
73+
*/
74+
export function filterSuppressedDiagnostics<T>(
75+
diagnostics: T[],
76+
getStartLine: (diag: T) => number,
77+
getMessage: (diag: T) => string,
78+
getLineText: GetLineText
79+
): T[] {
80+
return diagnostics.filter((diag) => {
81+
const line = getStartLine(diag);
82+
if (line === 0) {
83+
return true; // no preceding line to check
84+
}
85+
const prevLineText = getLineText(line - 1);
86+
if (prevLineText === undefined) {
87+
return true; // couldn't read the line — keep the diagnostic
88+
}
89+
const match = YAML_LINT_DISABLE_PATTERN.exec(prevLineText);
90+
if (!match) {
91+
return true; // no disable comment — keep the diagnostic
92+
}
93+
const specifiers = parseDisableSpecifiers(match[1]);
94+
return !shouldSuppressDiagnostic(specifiers, getMessage(diag));
95+
});
96+
}

src/extension.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getConflictingExtensions, showUninstallConflictsNotification } from './
2121
import { TelemetryErrorHandler, TelemetryOutputChannel } from './telemetry';
2222
import { createJSONSchemaStatusBarItem } from './schema-status-bar-item';
2323
import { initializeRecommendation } from './recommendation';
24+
import { filterSuppressedDiagnostics } from './diagnostic-filter';
2425

2526
export interface ISchemaAssociations {
2627
[pattern: string]: string[];
@@ -140,6 +141,27 @@ export function startClient(
140141
initializationOptions: {
141142
l10nPath,
142143
},
144+
middleware: {
145+
handleDiagnostics(uri, diagnostics, next) {
146+
const doc = workspace.textDocuments.find((d) => d.uri.toString() === uri.toString());
147+
const getLineText = (line: number): string | undefined => {
148+
try {
149+
return doc?.lineAt(line).text;
150+
} catch {
151+
return undefined;
152+
}
153+
};
154+
next(
155+
uri,
156+
filterSuppressedDiagnostics(
157+
diagnostics,
158+
(d) => d.range.start.line,
159+
(d) => d.message,
160+
getLineText
161+
)
162+
);
163+
},
164+
},
143165
};
144166

145167
// Create the language client and start it

0 commit comments

Comments
 (0)