Skip to content

Commit de35a01

Browse files
feat: add setting to control when linter runs (#848)
Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
1 parent 80b7f3e commit de35a01

File tree

13 files changed

+194
-2
lines changed

13 files changed

+194
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Added: `stylelint.run` setting to control when the linter runs ([#848](https://github.com/stylelint/vscode-stylelint/issues/848)).
56
- Added: notification prompting to restart the extension host when `stylelint.logLevel` setting changes ([#849](https://github.com/stylelint/vscode-stylelint/pull/849)).
67
- Added: warning when `stylelint.config` is an empty object ([#846](https://github.com/stylelint/vscode-stylelint/pull/846)).
78

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ Though relying on a [Stylelint configuration file](https://stylelint.io/user-gui
134134
135135
Controls whether this extension is enabled or not.
136136

137+
#### `stylelint.run`
138+
139+
> Type: `"onSave" | "onType"`
140+
> Default: `"onType"`
141+
142+
Controls when the linter runs.
143+
144+
- `onType`: Runs on each change as you type.
145+
- `onSave`: Only runs after you save a document.
146+
137147
#### `stylelint.logLevel`
138148

139149
> Type: `"error" | "warn" | "info" | "debug"`

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@
5656
"default": true,
5757
"description": "Control whether Stylelint is enabled or not."
5858
},
59+
"stylelint.run": {
60+
"scope": "resource",
61+
"type": "string",
62+
"enum": [
63+
"onSave",
64+
"onType"
65+
],
66+
"default": "onType",
67+
"description": "Control when the extension lints your files. onType lints files as you edit them, while onSave only lints when you save the file."
68+
},
5969
"stylelint.logLevel": {
6070
"scope": "machine",
6171
"type": "string",

src/server/config/default-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const defaultLanguageServerOptions: LanguageServerOptions = {
1717
reportDescriptionlessDisables: false,
1818
reportInvalidScopeDisables: false,
1919
reportNeedlessDisables: false,
20+
run: 'onType',
2021
rules: {
2122
customizations: [],
2223
},

src/server/services/lsp/__tests__/validator.service.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,75 @@ describe('ValidatorLspModule', () => {
312312
{ uri: document.uri, diagnostics: lintDiagnostics },
313313
]);
314314
});
315+
316+
describe('run mode', () => {
317+
it('handleDocumentOpened should always validate', async () => {
318+
const document = setDocument();
319+
const lintDiagnostics = [createDiagnostic('lint')];
320+
321+
options.setValidateLanguages(['css']);
322+
options.setRunMode('onSave');
323+
runner.setLintResult(document.uri, createDiagnosticsResult(lintDiagnostics));
324+
325+
await service.handleDocumentOpened(createChangeEvent(document));
326+
327+
expect(connection.sendDiagnosticsCalls).toEqual([
328+
{ uri: document.uri, diagnostics: lintDiagnostics },
329+
]);
330+
});
331+
332+
it('handleDocumentChanged should skip validation when run is onSave', async () => {
333+
const document = setDocument();
334+
335+
options.setValidateLanguages(['css']);
336+
options.setRunMode('onSave');
337+
runner.setLintResult(document.uri, createDiagnosticsResult([createDiagnostic('lint')]));
338+
339+
await service.handleDocumentChanged(createChangeEvent(document));
340+
341+
expect(runner.lintCalls).toHaveLength(0);
342+
});
343+
344+
it('handleDocumentChanged should validate when run is onType', async () => {
345+
const document = setDocument();
346+
const lintDiagnostics = [createDiagnostic('lint')];
347+
348+
options.setValidateLanguages(['css']);
349+
options.setRunMode('onType');
350+
runner.setLintResult(document.uri, createDiagnosticsResult(lintDiagnostics));
351+
352+
await service.handleDocumentChanged(createChangeEvent(document));
353+
354+
expect(connection.sendDiagnosticsCalls).toEqual([
355+
{ uri: document.uri, diagnostics: lintDiagnostics },
356+
]);
357+
});
358+
359+
it('handleDocumentSaved should validate when run is onSave', async () => {
360+
const document = setDocument();
361+
const lintDiagnostics = [createDiagnostic('lint')];
362+
363+
options.setValidateLanguages(['css']);
364+
options.setRunMode('onSave');
365+
runner.setLintResult(document.uri, createDiagnosticsResult(lintDiagnostics));
366+
367+
await service.handleDocumentSaved(createChangeEvent(document));
368+
369+
expect(connection.sendDiagnosticsCalls).toEqual([
370+
{ uri: document.uri, diagnostics: lintDiagnostics },
371+
]);
372+
});
373+
374+
it('handleDocumentSaved should skip validation when run is onType', async () => {
375+
const document = setDocument();
376+
377+
options.setValidateLanguages(['css']);
378+
options.setRunMode('onType');
379+
runner.setLintResult(document.uri, createDiagnosticsResult([createDiagnostic('lint')]));
380+
381+
await service.handleDocumentSaved(createChangeEvent(document));
382+
383+
expect(runner.lintCalls).toHaveLength(0);
384+
});
385+
});
315386
});

src/server/services/lsp/validator.service.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,27 @@ export class ValidatorLspService {
5656
void this.#validateAll();
5757
}
5858

59+
@textDocumentEvent('onDidOpen')
60+
async handleDocumentOpened({ document }: TextDocumentChangeEvent<TextDocument>): Promise<void> {
61+
await this.#validate(document);
62+
}
63+
5964
@textDocumentEvent('onDidChangeContent')
6065
async handleDocumentChanged({ document }: TextDocumentChangeEvent<TextDocument>): Promise<void> {
61-
await this.#validate(document);
66+
const options = await this.#options.getOptions(document.uri);
67+
68+
if (options.run === 'onType') {
69+
await this.#validate(document);
70+
}
71+
}
72+
73+
@textDocumentEvent('onDidSave')
74+
async handleDocumentSaved({ document }: TextDocumentChangeEvent<TextDocument>): Promise<void> {
75+
const options = await this.#options.getOptions(document.uri);
76+
77+
if (options.run === 'onSave') {
78+
await this.#validate(document);
79+
}
6280
}
6381

6482
@textDocumentEvent('onDidClose')

src/server/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export interface DidRegisterDocumentFormattingEditProviderNotificationParams {
7171
readonly options: LSP.DocumentFormattingRegistrationOptions;
7272
}
7373

74+
/**
75+
* Run mode for the linter. `onType` lints as you type, while `onSave` only
76+
* lints after saving a document.
77+
*/
78+
export type RunMode = 'onSave' | 'onType';
79+
7480
/**
7581
* Language server options.
7682
*/
@@ -89,6 +95,7 @@ export type LanguageServerOptions = {
8995
reportDescriptionlessDisables?: boolean;
9096
reportInvalidScopeDisables?: boolean;
9197
reportNeedlessDisables?: boolean;
98+
run: RunMode;
9299
rules?: {
93100
customizations?: RuleCustomization[];
94101
};

test/e2e/__tests__/run.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as assert from 'node:assert/strict';
2+
3+
import {
4+
openDocument,
5+
closeAllEditors,
6+
restoreFile,
7+
waitForDiagnostics,
8+
assertDiagnostics,
9+
waitForDiagnosticsLength,
10+
} from '../helpers.js';
11+
12+
import { Range, Position } from 'vscode';
13+
14+
describe('"stylelint.run" setting', () => {
15+
describe('when set to "onSave"', () => {
16+
restoreFile('run/test.css');
17+
18+
afterEach(async () => {
19+
await closeAllEditors();
20+
});
21+
22+
it('should not lint on type, but should lint on save', async () => {
23+
const editor = await openDocument('run/test.css');
24+
25+
const initialDiagnostics = await waitForDiagnostics(editor);
26+
27+
assertDiagnostics(initialDiagnostics, [
28+
{
29+
code: 'color-hex-length',
30+
codeDescription: 'https://stylelint.io/user-guide/rules/color-hex-length',
31+
message: 'Expected "#fff" to be "#ffffff" (color-hex-length)',
32+
range: [2, 9, 2, 13],
33+
severity: 'error',
34+
},
35+
]);
36+
37+
const success = await editor.edit((editBuilder) => {
38+
editBuilder.replace(new Range(new Position(2, 9), new Position(2, 13)), '#ffffff');
39+
});
40+
41+
assert.ok(success, 'Edit should succeed');
42+
43+
await waitForDiagnosticsLength(editor.document.uri, 1);
44+
45+
await editor.document.save();
46+
47+
await waitForDiagnosticsLength(editor.document.uri, 0);
48+
});
49+
});
50+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"stylelint.run": "onSave"
3+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
/** @type {import('stylelint').Config} */
4+
const config = {
5+
rules: {
6+
'color-hex-length': 'long',
7+
},
8+
};
9+
10+
module.exports = config;

0 commit comments

Comments
 (0)