From 6aa680d7594710023dd9425987b6e1a7d77f1f0a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 23 Jan 2021 13:54:47 -0500 Subject: [PATCH 01/12] Implement eslint.rules.customizations - with overrides --- README.md | 23 ++++++++++++ client/src/extension.ts | 19 ++++++++++ package.json | 44 ++++++++++++++++++++++ playgrounds/6.0/.vscode/settings.json | 7 ++++ server/src/eslintServer.ts | 53 ++++++++++++++++++++++++++- 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6331cac0..699a5472 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,29 @@ This extension contributes the following variables to the [settings](https://cod - `all`: fixes all possible problems by revalidating the file's content. This executes the same code path as running eslint with the `--fix` option in the terminal and therefore can take some time. This is the default value. - `problems`: fixes only the currently known fixable problems as long as their textual edits are non overlapping. This mode is a lot faster but very likely only fixes parts of the problems. +- `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. This is an array that allows two kinds of glob patterns: + - `"override`": Overrides for rules with names that match, factoring in asterisks: `{ "override": "no-*", "severity": "warn" }` + - `"ignore"`: Excludes rules matching a glob from previous overrides: `{ "ignore": "*jsx*" }` + + In this example, all rules are overridden to warnings: + + ```json + "eslint.rules.customizations": [ + { "override": "*", "severity": "warn" } + ] + ``` + + In this example, with the exception of `semi` rules and `radix`, all all `no-` rules are warnings and other rules are info: + + ```json + "eslint.rules.customizations": [ + { "override": "*", "severity": "warn" }, + { "override": "no-*", "severity": "info" }, + { "ignore": "*semi*" }, + { "ignore": "radix" } + ] + ``` + - `eslint.format.enable` (@since 2.0.0): uses ESlint as a formatter for files that are validated by ESLint. If enabled please ensure to disable other formatters if you want to make this the default. A good way to do so is to add the following setting `"[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }` for JavaScript. For TypeScript you need to add `"[typescript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }`. - `eslint.onIgnoredFiles` (@since 2.0.10): used to control whether warnings should be generated when trying to lint ignored files. Default is `off`. Can be set to `warn`. - `editor.codeActionsOnSave` (@since 2.0.0): this setting now supports an entry `source.fixAll.eslint`. If set to true all auto-fixable ESLint errors from all plugins will be fixed on save. You can also selectively enable and disabled specific languages using VS Code's language scoped settings. To disable `codeActionsOnSave` for HTML files use the following setting: diff --git a/client/src/extension.ts b/client/src/extension.ts index 6c7d66dc..acbefd0c 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -181,6 +181,23 @@ enum ConfirmationSelection { alwaysAllow = 4 } +enum RuleSeverity { + info = 'info', + warn = 'warn', + error = 'error' +} + +type RuleCustomization = RuleIgnore | RuleOverride; + +interface RuleIgnore { + ignore: string; +} + +interface RuleOverride { + override: string; + severity: RuleSeverity; +} + interface ConfigurationSettings { validate: Validate; packageManager: 'npm' | 'yarn' | 'pnpm'; @@ -190,6 +207,7 @@ interface ConfigurationSettings { quiet: boolean; onIgnoredFiles: ESLintSeverity; options: any | undefined; + rulesCustomizations: RuleCustomization[]; run: RunValues; nodePath: string | null; workspaceFolder: WorkspaceFolder | undefined; @@ -1441,6 +1459,7 @@ function realActivate(context: ExtensionContext): void { quiet: config.get('quiet', false), onIgnoredFiles: ESLintSeverity.from(config.get('onIgnoredFiles', ESLintSeverity.off)), options: config.get('options', {}), + rulesCustomizations: config.get('rules.customizations', []), run: config.get('run', 'onType'), nodePath: config.get('nodePath', null), workingDirectory: undefined, diff --git a/package.json b/package.json index d33ba86b..71249878 100644 --- a/package.json +++ b/package.json @@ -375,6 +375,50 @@ "type": "boolean", "default": false, "description": "Enables ESLint as a formatter." + }, + "eslint.rules.customizations": { + "description": "Override the severity of one or more rules reported by this extension, regardless of the project's ESLint config. Use globs to apply default severities for multiple rules.", + "items": { + "properties": { + "anyOf": [ + { + "properties": { + "ignore": { + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "severity": { + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ] + }, + "rule": { + "type": "string" + } + }, + "type": "object" + } + ] + }, + "type": "object" + }, + "scope": "resource", + "type": "array", + "additionalProperties": { + "type": "string", + "enum": [ + "info", + "warn", + "error" + ] + } } } }, diff --git a/playgrounds/6.0/.vscode/settings.json b/playgrounds/6.0/.vscode/settings.json index 7cb29a2d..a9b7825f 100644 --- a/playgrounds/6.0/.vscode/settings.json +++ b/playgrounds/6.0/.vscode/settings.json @@ -39,6 +39,13 @@ "location": "separateLine" }, "eslint.quiet": false, + "eslint.rules.customizations": [ + { "override": "*", "severity": "info" }, + { "override": "no-*", "severity": "warn" }, + { "ignore": "*console*" }, + { "ignore": "*semi*" }, + { "ignore": "radix" } + ], "editor.codeActionsOnSaveTimeout": 2000, "eslint.format.enable": true, "eslint.onIgnoredFiles": "off", diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index f282b0da..8db9a2bc 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -224,6 +224,23 @@ interface CodeActionsOnSaveSettings { mode: CodeActionsOnSaveMode } +enum RuleSeverity { + info = 'info', + warn = 'warn', + error = 'error' +} + +type RuleCustomization = RuleIgnore | RuleOverride; + +interface RuleIgnore { + ignore: string; +} + +interface RuleOverride { + override: string; + severity: RuleSeverity; +} + interface CommonSettings { validate: Validate; packageManager: 'npm' | 'yarn' | 'pnpm'; @@ -233,6 +250,7 @@ interface CommonSettings { quiet: boolean; onIgnoredFiles: ESLintSeverity; options: ESLintOptions | undefined; + rulesCustomizations: RuleCustomization[]; run: RunValues; nodePath: string | null; workspaceFolder: WorkspaceFolder | undefined; @@ -368,7 +386,27 @@ function loadNodeModule(moduleName: string): T | undefined { return undefined; } -function makeDiagnostic(problem: ESLintProblem): Diagnostic { +function asteriskMatches(matcher: string, ruleId: string) { + return new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); +} + +function getSeverityOverride(ruleId: string, customizations: RuleCustomization[]) { + let result: RuleSeverity | undefined; + + for (const customization of customizations) { + if ('ignore' in customization) { + if (asteriskMatches(customization.ignore, ruleId)) { + result = undefined; + } + } else if (asteriskMatches(customization.override, ruleId)) { + result = customization.severity; + } + } + + return result; +} + +function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): Diagnostic { const message = problem.message; const startLine = Is.nullOrUndefined(problem.line) ? 0 : Math.max(0, problem.line - 1); const startChar = Is.nullOrUndefined(problem.column) ? 0 : Math.max(0, problem.column - 1); @@ -395,6 +433,12 @@ function makeDiagnostic(problem: ESLintProblem): Diagnostic { result.tags = [DiagnosticTag.Unnecessary]; } } + + const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations); + if (severityOverride) { + result.severity = convertSeverity(severityOverride); + } + return result; } @@ -465,13 +509,18 @@ function recordCodeAction(document: TextDocument, diagnostic: Diagnostic, proble }); } -function convertSeverity(severity: number): DiagnosticSeverity { +function convertSeverity(severity: number | RuleSeverity): DiagnosticSeverity { + // RuleSeverity concerns an overridden rule. A number is direct from ESLint. switch (severity) { // Eslint 1 is warning case 1: + case RuleSeverity.warn: return DiagnosticSeverity.Warning; case 2: + case RuleSeverity.error: return DiagnosticSeverity.Error; + case RuleSeverity.info: + return DiagnosticSeverity.Information; default: return DiagnosticSeverity.Error; } From 54553543d82030d5c8aec877f09eaa91e27a41cb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 23 Jan 2021 14:13:15 -0500 Subject: [PATCH 02/12] Renamed from 'ignore' to 'reset' --- README.md | 6 +++--- client/src/extension.ts | 6 +++--- package.json | 20 ++++++++++---------- server/src/eslintServer.ts | 18 +++++++++--------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 699a5472..3f4a7909 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ This extension contributes the following variables to the [settings](https://cod - `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. This is an array that allows two kinds of glob patterns: - `"override`": Overrides for rules with names that match, factoring in asterisks: `{ "override": "no-*", "severity": "warn" }` - - `"ignore"`: Excludes rules matching a glob from previous overrides: `{ "ignore": "*jsx*" }` + - `"reset"`: Excludes rules matching a glob from previous overrides: `{ "reset": "*jsx*" }` In this example, all rules are overridden to warnings: @@ -164,8 +164,8 @@ This extension contributes the following variables to the [settings](https://cod "eslint.rules.customizations": [ { "override": "*", "severity": "warn" }, { "override": "no-*", "severity": "info" }, - { "ignore": "*semi*" }, - { "ignore": "radix" } + { "reset": "*semi*" }, + { "reset": "radix" } ] ``` diff --git a/client/src/extension.ts b/client/src/extension.ts index acbefd0c..1df374c0 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -187,10 +187,10 @@ enum RuleSeverity { error = 'error' } -type RuleCustomization = RuleIgnore | RuleOverride; +type RuleCustomization = RuleOverride | RuleReset; -interface RuleIgnore { - ignore: string; +interface RuleReset { + reset: string; } interface RuleOverride { diff --git a/package.json b/package.json index 71249878..9148f304 100644 --- a/package.json +++ b/package.json @@ -381,14 +381,6 @@ "items": { "properties": { "anyOf": [ - { - "properties": { - "ignore": { - "type": "string" - } - }, - "type": "object" - }, { "properties": { "severity": { @@ -399,12 +391,20 @@ "verbose" ] }, - "rule": { + "override": { "type": "string" } }, "type": "object" - } + }, + { + "properties": { + "reset": { + "type": "string" + } + }, + "type": "object" + }, ] }, "type": "object" diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index 8db9a2bc..fbf04542 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -230,10 +230,10 @@ enum RuleSeverity { error = 'error' } -type RuleCustomization = RuleIgnore | RuleOverride; +type RuleCustomization = RuleOverride | RuleReset; -interface RuleIgnore { - ignore: string; +interface RuleReset { + reset: string; } interface RuleOverride { @@ -394,12 +394,12 @@ function getSeverityOverride(ruleId: string, customizations: RuleCustomization[] let result: RuleSeverity | undefined; for (const customization of customizations) { - if ('ignore' in customization) { - if (asteriskMatches(customization.ignore, ruleId)) { - result = undefined; + if ('override' in customization) { + if (asteriskMatches(customization.override, ruleId)) { + result = customization.severity; } - } else if (asteriskMatches(customization.override, ruleId)) { - result = customization.severity; + } else if (asteriskMatches(customization.reset, ruleId)) { + result = undefined; } } @@ -1365,7 +1365,7 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib // Filter out warnings when quiet mode is enabled return; } - const diagnostic = makeDiagnostic(problem); + const diagnostic = makeDiagnostic(settings, problem); diagnostics.push(diagnostic); if (fixTypes !== undefined && CLIEngine.hasRule(cli) && problem.ruleId !== undefined && problem.fix !== undefined) { const rule = cli.getRules().get(problem.ruleId); From 7004ef6de82500b25ec02bfc8d77d9aa2f50e46a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 23 Jan 2021 14:13:43 -0500 Subject: [PATCH 03/12] Fixed compile complaints --- package.json | 2 +- playgrounds/6.0/.vscode/settings.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9148f304..4b86925a 100644 --- a/package.json +++ b/package.json @@ -404,7 +404,7 @@ } }, "type": "object" - }, + } ] }, "type": "object" diff --git a/playgrounds/6.0/.vscode/settings.json b/playgrounds/6.0/.vscode/settings.json index a9b7825f..74e0a102 100644 --- a/playgrounds/6.0/.vscode/settings.json +++ b/playgrounds/6.0/.vscode/settings.json @@ -42,9 +42,9 @@ "eslint.rules.customizations": [ { "override": "*", "severity": "info" }, { "override": "no-*", "severity": "warn" }, - { "ignore": "*console*" }, - { "ignore": "*semi*" }, - { "ignore": "radix" } + { "reset": "*console*" }, + { "reset": "*semi*" }, + { "reset": "radix" } ], "editor.codeActionsOnSaveTimeout": 2000, "eslint.format.enable": true, From fd013ab35c05499630bea506c30f651928edf7f9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 26 Jan 2021 23:39:32 -0500 Subject: [PATCH 04/12] Added a simple rule severy cache using a Map --- server/src/eslintServer.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index fbf04542..7b003f14 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -386,11 +386,17 @@ function loadNodeModule(moduleName: string): T | undefined { return undefined; } +type RuleSeverityCache = Map; + function asteriskMatches(matcher: string, ruleId: string) { return new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); } -function getSeverityOverride(ruleId: string, customizations: RuleCustomization[]) { +function getSeverityOverride(ruleId: string, customizations: RuleCustomization[], ruleSeverityCache: RuleSeverityCache) { + if (ruleSeverityCache.has(ruleId)) { + return ruleSeverityCache.get(ruleId); + } + let result: RuleSeverity | undefined; for (const customization of customizations) { @@ -403,10 +409,12 @@ function getSeverityOverride(ruleId: string, customizations: RuleCustomization[] } } + ruleSeverityCache.set(ruleId, result); + return result; } -function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): Diagnostic { +function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem, ruleSeverityCache: RuleSeverityCache): Diagnostic { const message = problem.message; const startLine = Is.nullOrUndefined(problem.line) ? 0 : Math.max(0, problem.line - 1); const startChar = Is.nullOrUndefined(problem.column) ? 0 : Math.max(0, problem.column - 1); @@ -434,7 +442,7 @@ function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): } } - const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations); + const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations, ruleSeverityCache); if (severityOverride) { result.severity = convertSeverity(severityOverride); } @@ -1342,6 +1350,7 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib const content = document.getText(); const uri = document.uri; const file = getFilePath(document); + const ruleSeverityCache: RuleSeverityCache = new Map(); withCLIEngine((cli) => { codeActions.delete(uri); @@ -1365,7 +1374,7 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib // Filter out warnings when quiet mode is enabled return; } - const diagnostic = makeDiagnostic(settings, problem); + const diagnostic = makeDiagnostic(settings, problem, ruleSeverityCache); diagnostics.push(diagnostic); if (fixTypes !== undefined && CLIEngine.hasRule(cli) && problem.ruleId !== undefined && problem.fix !== undefined) { const rule = cli.getRules().get(problem.ruleId); From bbc3eb072892ee75e8690ee58d205df8a9612720 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 26 Jan 2021 23:59:04 -0500 Subject: [PATCH 05/12] Switched map to only clear cache on new rulesCustomizations stringification --- server/src/eslintServer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index 7b003f14..e480e2bd 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -1330,6 +1330,8 @@ const ruleDocData: { urls: new Map() }; +const ruleSeverityCache: RuleSeverityCache = new Map(); +let ruleCustomizationsKey: string | undefined; const validFixTypes = new Set(['problem', 'suggestion', 'layout']); function validate(document: TextDocument, settings: TextDocumentSettings & { library: ESLintModule }, publishDiagnostics: boolean = true): void { @@ -1347,10 +1349,15 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib } } + const newRuleCustomizationsKey = JSON.stringify(settings.rulesCustomizations); + if (ruleCustomizationsKey !== newRuleCustomizationsKey) { + ruleCustomizationsKey = newRuleCustomizationsKey; + ruleSeverityCache.clear(); + } + const content = document.getText(); const uri = document.uri; const file = getFilePath(document); - const ruleSeverityCache: RuleSeverityCache = new Map(); withCLIEngine((cli) => { codeActions.delete(uri); From 0cc0a8a5c4056eee1139b756f6d156adea60de80 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 27 Jan 2021 00:00:31 -0500 Subject: [PATCH 06/12] Used LinkedMap as suggested --- server/src/eslintServer.ts | 5 +- server/src/linkedMap.ts | 448 +++++++++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 server/src/linkedMap.ts diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index e480e2bd..af82d3a8 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -23,6 +23,7 @@ import * as fs from 'fs'; import { execSync } from 'child_process'; import { EOL } from 'os'; import { stringDiff } from './diff'; +import { LinkedMap } from './linkedMap'; namespace Is { const toString = Object.prototype.toString; @@ -386,7 +387,7 @@ function loadNodeModule(moduleName: string): T | undefined { return undefined; } -type RuleSeverityCache = Map; +type RuleSeverityCache = LinkedMap; function asteriskMatches(matcher: string, ruleId: string) { return new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); @@ -1330,7 +1331,7 @@ const ruleDocData: { urls: new Map() }; -const ruleSeverityCache: RuleSeverityCache = new Map(); +const ruleSeverityCache: RuleSeverityCache = new LinkedMap(); let ruleCustomizationsKey: string | undefined; const validFixTypes = new Set(['problem', 'suggestion', 'layout']); diff --git a/server/src/linkedMap.ts b/server/src/linkedMap.ts new file mode 100644 index 00000000..2c464092 --- /dev/null +++ b/server/src/linkedMap.ts @@ -0,0 +1,448 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface Item { + previous: Item | undefined; + next: Item | undefined; + key: K; + value: V; +} + +export namespace Touch { + export const None: 0 = 0; + export const First: 1 = 1; + export const AsOld: 1 = Touch.First; + export const Last: 2 = 2; + export const AsNew: 2 = Touch.Last; +} + +export type Touch = 0 | 1 | 2; + +export class LinkedMap implements Map { + + readonly [Symbol.toStringTag] = 'LinkedMap'; + + private _map: Map>; + private _head: Item | undefined; + private _tail: Item | undefined; + private _size: number; + + private _state: number; + + public constructor() { + this._map = new Map>(); + this._head = undefined; + this._tail = undefined; + this._size = 0; + this._state = 0; + } + + public clear(): void { + this._map.clear(); + this._head = undefined; + this._tail = undefined; + this._size = 0; + this._state++; + } + + public isEmpty(): boolean { + return !this._head && !this._tail; + } + + public get size(): number { + return this._size; + } + + public get first(): V | undefined { + return this._head?.value; + } + + public get last(): V | undefined { + return this._tail?.value; + } + + public has(key: K): boolean { + return this._map.has(key); + } + + public get(key: K, touch: Touch = Touch.None): V | undefined { + const item = this._map.get(key); + if (!item) { + return undefined; + } + if (touch !== Touch.None) { + this.touch(item, touch); + } + return item.value; + } + + public set(key: K, value: V, touch: Touch = Touch.None): this { + let item = this._map.get(key); + if (item) { + item.value = value; + if (touch !== Touch.None) { + this.touch(item, touch); + } + } else { + item = { key, value, next: undefined, previous: undefined }; + switch (touch) { + case Touch.None: + this.addItemLast(item); + break; + case Touch.First: + this.addItemFirst(item); + break; + case Touch.Last: + this.addItemLast(item); + break; + default: + this.addItemLast(item); + break; + } + this._map.set(key, item); + this._size++; + } + return this; + } + + public delete(key: K): boolean { + return !!this.remove(key); + } + + public remove(key: K): V | undefined { + const item = this._map.get(key); + if (!item) { + return undefined; + } + this._map.delete(key); + this.removeItem(item); + this._size--; + return item.value; + } + + public shift(): V | undefined { + if (!this._head && !this._tail) { + return undefined; + } + if (!this._head || !this._tail) { + throw new Error('Invalid list'); + } + const item = this._head; + this._map.delete(item.key); + this.removeItem(item); + this._size--; + return item.value; + } + + public forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: any): void { + const state = this._state; + let current = this._head; + while (current) { + if (thisArg) { + callbackfn.bind(thisArg)(current.value, current.key, this); + } else { + callbackfn(current.value, current.key, this); + } + if (this._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + current = current.next; + } + } + + public keys(): IterableIterator { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result = { value: current.key, done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + public values(): IterableIterator { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result = { value: current.value, done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + public entries(): IterableIterator<[K, V]> { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator<[K, V]> = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult<[K, V]> { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + public [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } + + protected trimOld(newSize: number) { + if (newSize >= this.size) { + return; + } + if (newSize === 0) { + this.clear(); + return; + } + let current = this._head; + let currentSize = this.size; + while (current && currentSize > newSize) { + this._map.delete(current.key); + current = current.next; + currentSize--; + } + this._head = current; + this._size = currentSize; + if (current) { + current.previous = undefined; + } + this._state++; + } + + private addItemFirst(item: Item): void { + // First time Insert + if (!this._head && !this._tail) { + this._tail = item; + } else if (!this._head) { + throw new Error('Invalid list'); + } else { + item.next = this._head; + this._head.previous = item; + } + this._head = item; + this._state++; + } + + private addItemLast(item: Item): void { + // First time Insert + if (!this._head && !this._tail) { + this._head = item; + } else if (!this._tail) { + throw new Error('Invalid list'); + } else { + item.previous = this._tail; + this._tail.next = item; + } + this._tail = item; + this._state++; + } + + private removeItem(item: Item): void { + if (item === this._head && item === this._tail) { + this._head = undefined; + this._tail = undefined; + } + else if (item === this._head) { + // This can only happend if size === 1 which is handle + // by the case above. + if (!item.next) { + throw new Error('Invalid list'); + } + item.next.previous = undefined; + this._head = item.next; + } + else if (item === this._tail) { + // This can only happend if size === 1 which is handle + // by the case above. + if (!item.previous) { + throw new Error('Invalid list'); + } + item.previous.next = undefined; + this._tail = item.previous; + } + else { + const next = item.next; + const previous = item.previous; + if (!next || !previous) { + throw new Error('Invalid list'); + } + next.previous = previous; + previous.next = next; + } + item.next = undefined; + item.previous = undefined; + this._state++; + } + + private touch(item: Item, touch: Touch): void { + if (!this._head || !this._tail) { + throw new Error('Invalid list'); + } + if ((touch !== Touch.First && touch !== Touch.Last)) { + return; + } + + if (touch === Touch.First) { + if (item === this._head) { + return; + } + + const next = item.next; + const previous = item.previous; + + // Unlink the item + if (item === this._tail) { + // previous must be defined since item was not head but is tail + // So there are more than on item in the map + previous!.next = undefined; + this._tail = previous; + } + else { + // Both next and previous are not undefined since item was neither head nor tail. + next!.previous = previous; + previous!.next = next; + } + + // Insert the node at head + item.previous = undefined; + item.next = this._head; + this._head.previous = item; + this._head = item; + this._state++; + } else if (touch === Touch.Last) { + if (item === this._tail) { + return; + } + + const next = item.next; + const previous = item.previous; + + // Unlink the item. + if (item === this._head) { + // next must be defined since item was not tail but is head + // So there are more than on item in the map + next!.previous = undefined; + this._head = next; + } else { + // Both next and previous are not undefined since item was neither head nor tail. + next!.previous = previous; + previous!.next = next; + } + item.next = undefined; + item.previous = this._tail; + this._tail.next = item; + this._tail = item; + this._state++; + } + } + + public toJSON(): [K, V][] { + const data: [K, V][] = []; + + this.forEach((value, key) => { + data.push([key, value]); + }); + + return data; + } + + public fromJSON(data: [K, V][]): void { + this.clear(); + + for (const [key, value] of data) { + this.set(key, value); + } + } +} + +export class LRUCache extends LinkedMap { + + private _limit: number; + private _ratio: number; + + public constructor(limit: number, ratio: number = 1) { + super(); + this._limit = limit; + this._ratio = Math.min(Math.max(0, ratio), 1); + } + + public get limit(): number { + return this._limit; + } + + public set limit(limit: number) { + this._limit = limit; + this.checkTrim(); + } + + public get ratio(): number { + return this._ratio; + } + + public set ratio(ratio: number) { + this._ratio = Math.min(Math.max(0, ratio), 1); + this.checkTrim(); + } + + public get(key: K, touch: Touch = Touch.AsNew): V | undefined { + return super.get(key, touch); + } + + public peek(key: K): V | undefined { + return super.get(key, Touch.None); + } + + public set(key: K, value: V): this { + super.set(key, value, Touch.Last); + this.checkTrim(); + return this; + } + + private checkTrim() { + if (this.size > this._limit) { + this.trimOld(Math.round(this._limit * this._ratio)); + } + } +} \ No newline at end of file From 3875dbde2fb6730e92b01b2c590b3893c5448a56 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 27 Jan 2021 23:54:29 -0500 Subject: [PATCH 07/12] Used an LRU cache as suggested --- server/src/eslintServer.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index af82d3a8..a7610618 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -23,7 +23,7 @@ import * as fs from 'fs'; import { execSync } from 'child_process'; import { EOL } from 'os'; import { stringDiff } from './diff'; -import { LinkedMap } from './linkedMap'; +import { LRUCache } from './linkedMap'; namespace Is { const toString = Object.prototype.toString; @@ -387,13 +387,14 @@ function loadNodeModule(moduleName: string): T | undefined { return undefined; } -type RuleSeverityCache = LinkedMap; +const ruleSeverityCache = new LRUCache(1024); +let ruleCustomizationsKey: string | undefined; function asteriskMatches(matcher: string, ruleId: string) { return new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); } -function getSeverityOverride(ruleId: string, customizations: RuleCustomization[], ruleSeverityCache: RuleSeverityCache) { +function getSeverityOverride(ruleId: string, customizations: RuleCustomization[]) { if (ruleSeverityCache.has(ruleId)) { return ruleSeverityCache.get(ruleId); } @@ -415,7 +416,7 @@ function getSeverityOverride(ruleId: string, customizations: RuleCustomization[] return result; } -function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem, ruleSeverityCache: RuleSeverityCache): Diagnostic { +function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): Diagnostic { const message = problem.message; const startLine = Is.nullOrUndefined(problem.line) ? 0 : Math.max(0, problem.line - 1); const startChar = Is.nullOrUndefined(problem.column) ? 0 : Math.max(0, problem.column - 1); @@ -443,7 +444,7 @@ function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem, } } - const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations, ruleSeverityCache); + const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations); if (severityOverride) { result.severity = convertSeverity(severityOverride); } @@ -1331,9 +1332,6 @@ const ruleDocData: { urls: new Map() }; -const ruleSeverityCache: RuleSeverityCache = new LinkedMap(); -let ruleCustomizationsKey: string | undefined; - const validFixTypes = new Set(['problem', 'suggestion', 'layout']); function validate(document: TextDocument, settings: TextDocumentSettings & { library: ESLintModule }, publishDiagnostics: boolean = true): void { const newOptions: CLIOptions = Object.assign(Object.create(null), settings.options); @@ -1382,7 +1380,7 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib // Filter out warnings when quiet mode is enabled return; } - const diagnostic = makeDiagnostic(settings, problem, ruleSeverityCache); + const diagnostic = makeDiagnostic(settings, problem); diagnostics.push(diagnostic); if (fixTypes !== undefined && CLIEngine.hasRule(cli) && problem.ruleId !== undefined && problem.fix !== undefined) { const rule = cli.getRules().get(problem.ruleId); From 6fbd59f85773af6703db275ee4dd5c51eff17bc3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 12 Mar 2021 09:25:24 -0500 Subject: [PATCH 08/12] Dedup enum and sanitize inputs --- client/src/extension.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 1df374c0..969e9dcd 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -181,12 +181,6 @@ enum ConfirmationSelection { alwaysAllow = 4 } -enum RuleSeverity { - info = 'info', - warn = 'warn', - error = 'error' -} - type RuleCustomization = RuleOverride | RuleReset; interface RuleReset { @@ -195,7 +189,7 @@ interface RuleReset { interface RuleOverride { override: string; - severity: RuleSeverity; + severity: ESLintSeverity; } interface ConfigurationSettings { @@ -417,6 +411,27 @@ function computeValidate(textDocument: TextDocument): Validate { return Validate.off; } +function parseRulesCustomizations(rawConfig: Partial[] | unknown): RuleCustomization[] { + if (!rawConfig || !Array.isArray(rawConfig)) { + return []; + } + + return rawConfig.map(rawValue => { + if ('reset' in rawValue && typeof rawValue.reset === 'string') { + return { reset: rawValue.reset }; + } + + if ('override' in rawValue && typeof rawValue.override === 'string' && 'severity' in rawValue && typeof rawValue.severity === 'string') { + return { + override: rawValue.override, + severity: rawValue.severity, + }; + } + + return undefined; + }).filter((value): value is RuleCustomization => !!value); +} + let taskProvider: TaskProvider; const eslintExecutionKey = 'eslintLibraries'; @@ -1459,7 +1474,7 @@ function realActivate(context: ExtensionContext): void { quiet: config.get('quiet', false), onIgnoredFiles: ESLintSeverity.from(config.get('onIgnoredFiles', ESLintSeverity.off)), options: config.get('options', {}), - rulesCustomizations: config.get('rules.customizations', []), + rulesCustomizations: parseRulesCustomizations(config.get('rules.customizations')), run: config.get('run', 'onType'), nodePath: config.get('nodePath', null), workingDirectory: undefined, From 67ead7acc3812290de18b6e4629c575622ef4c8f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 12 Mar 2021 12:36:16 -0500 Subject: [PATCH 09/12] Simplified type and allowed bang exclusions --- README.md | 14 ++--- client/src/extension.ts | 28 +++++----- package.json | 50 ++++++----------- playgrounds/6.0/.vscode/settings.json | 10 ++-- server/src/eslintServer.ts | 77 ++++++++++++++++++--------- 5 files changed, 95 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 3f4a7909..749a1f5c 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,10 @@ This extension contributes the following variables to the [settings](https://cod - `all`: fixes all possible problems by revalidating the file's content. This executes the same code path as running eslint with the `--fix` option in the terminal and therefore can take some time. This is the default value. - `problems`: fixes only the currently known fixable problems as long as their textual edits are non overlapping. This mode is a lot faster but very likely only fixes parts of the problems. -- `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. This is an array that allows two kinds of glob patterns: - - `"override`": Overrides for rules with names that match, factoring in asterisks: `{ "override": "no-*", "severity": "warn" }` - - `"reset"`: Excludes rules matching a glob from previous overrides: `{ "reset": "*jsx*" }` +- `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. contain two properties: + - `"rule`": Select on rules with names that match, factoring in asterisks as wildcards: `{ "override": "no-*", "severity": "warn" }` + - Prefix the query with a `"!"` to target all rules that _don't_ match the query: `{ "override": "!no-*", "severity": "info" }` + - `"override"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"reset"`s them to their original severity In this example, all rules are overridden to warnings: @@ -158,14 +159,13 @@ This extension contributes the following variables to the [settings](https://cod ] ``` - In this example, with the exception of `semi` rules and `radix`, all all `no-` rules are warnings and other rules are info: + In this example, `no-` rules are informative, other rules are downgraded, and `"radix"` is reset: ```json "eslint.rules.customizations": [ - { "override": "*", "severity": "warn" }, { "override": "no-*", "severity": "info" }, - { "reset": "*semi*" }, - { "reset": "radix" } + { "override": "!no-*", "severity": "downgrade" }, + { "reset": "radix", "severity": "reset" } ] ``` diff --git a/client/src/extension.ts b/client/src/extension.ts index 969e9dcd..cfc5b797 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -181,15 +181,21 @@ enum ConfirmationSelection { alwaysAllow = 4 } -type RuleCustomization = RuleOverride | RuleReset; +enum RuleSeverity { + // Original ESLint values + info = 'info', + warn = 'warn', + error = 'error', -interface RuleReset { - reset: string; + // Added override changes + reset = 'reset', + downgrade = 'downgrade', + upgrade = 'upgrade' } -interface RuleOverride { - override: string; - severity: ESLintSeverity; +interface RuleCustomization { + rule: string; + override: RuleSeverity; } interface ConfigurationSettings { @@ -411,20 +417,16 @@ function computeValidate(textDocument: TextDocument): Validate { return Validate.off; } -function parseRulesCustomizations(rawConfig: Partial[] | unknown): RuleCustomization[] { +function parseRulesCustomizations(rawConfig: unknown): RuleCustomization[] { if (!rawConfig || !Array.isArray(rawConfig)) { return []; } return rawConfig.map(rawValue => { - if ('reset' in rawValue && typeof rawValue.reset === 'string') { - return { reset: rawValue.reset }; - } - - if ('override' in rawValue && typeof rawValue.override === 'string' && 'severity' in rawValue && typeof rawValue.severity === 'string') { + if ('override' in rawValue && typeof rawValue.override === 'string' && 'rule' in rawValue && typeof rawValue.rule === 'string') { return { override: rawValue.override, - severity: rawValue.severity, + rule: rawValue.rule, }; } diff --git a/package.json b/package.json index 4b86925a..182c7af7 100644 --- a/package.json +++ b/package.json @@ -380,45 +380,25 @@ "description": "Override the severity of one or more rules reported by this extension, regardless of the project's ESLint config. Use globs to apply default severities for multiple rules.", "items": { "properties": { - "anyOf": [ - { - "properties": { - "severity": { - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ] - }, - "override": { - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "reset": { - "type": "string" - } - }, - "type": "object" - } - ] + "override": { + "enum": [ + "downgrade", + "error", + "info", + "reset", + "upgrade", + "warn" + ], + "type": "string" + }, + "rule": { + "type": "string" + } }, "type": "object" }, "scope": "resource", - "type": "array", - "additionalProperties": { - "type": "string", - "enum": [ - "info", - "warn", - "error" - ] - } + "type": "array" } } }, diff --git a/playgrounds/6.0/.vscode/settings.json b/playgrounds/6.0/.vscode/settings.json index 74e0a102..5bd2a299 100644 --- a/playgrounds/6.0/.vscode/settings.json +++ b/playgrounds/6.0/.vscode/settings.json @@ -40,11 +40,11 @@ }, "eslint.quiet": false, "eslint.rules.customizations": [ - { "override": "*", "severity": "info" }, - { "override": "no-*", "severity": "warn" }, - { "reset": "*console*" }, - { "reset": "*semi*" }, - { "reset": "radix" } + { "rule": "*", "override": "info" }, + { "rule": "!no-*", "override": "downgrade" }, + { "rule": "*console*", "override": "upgrade" }, + { "rule": "*semi*", "override": "reset" }, + { "rule": "radix", "override": "reset" } ], "editor.codeActionsOnSaveTimeout": 2000, "eslint.format.enable": true, diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index a7610618..f928e9c0 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -226,20 +226,21 @@ interface CodeActionsOnSaveSettings { } enum RuleSeverity { + // Original ESLint values info = 'info', warn = 'warn', - error = 'error' -} - -type RuleCustomization = RuleOverride | RuleReset; + error = 'error', -interface RuleReset { - reset: string; + // Added override changes + off = 'off', + reset = 'reset', + downgrade = 'downgrade', + upgrade = 'upgrade' } -interface RuleOverride { - override: string; - severity: RuleSeverity; +interface RuleCustomization { + rule: string; + override: RuleSeverity; } interface CommonSettings { @@ -391,7 +392,9 @@ const ruleSeverityCache = new LRUCache(1024); let ruleCustomizationsKey: string | undefined; function asteriskMatches(matcher: string, ruleId: string) { - return new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); + return matcher.startsWith('!') + ? !(new RegExp(`^${matcher.slice(1).replace(/\*/g, '.*')}$`, 'g').test(ruleId)) + : new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId); } function getSeverityOverride(ruleId: string, customizations: RuleCustomization[]) { @@ -402,12 +405,8 @@ function getSeverityOverride(ruleId: string, customizations: RuleCustomization[] let result: RuleSeverity | undefined; for (const customization of customizations) { - if ('override' in customization) { - if (asteriskMatches(customization.override, ruleId)) { - result = customization.severity; - } - } else if (asteriskMatches(customization.reset, ruleId)) { - result = undefined; + if (asteriskMatches(customization.rule, ruleId)) { + result = customization.override; } } @@ -424,7 +423,7 @@ function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): const endChar = Is.nullOrUndefined(problem.endColumn) ? startChar : Math.max(0, problem.endColumn - 1); const result: Diagnostic = { message: message, - severity: convertSeverity(problem.severity), + severity: convertSeverityToDiagnosticWithOverride(problem.severity, getSeverityOverride(problem.ruleId, settings.rulesCustomizations)), source: 'eslint', range: { start: { line: startLine, character: startChar }, @@ -444,11 +443,6 @@ function makeDiagnostic(settings: TextDocumentSettings, problem: ESLintProblem): } } - const severityOverride = getSeverityOverride(problem.ruleId, settings.rulesCustomizations); - if (severityOverride) { - result.severity = convertSeverity(severityOverride); - } - return result; } @@ -519,7 +513,37 @@ function recordCodeAction(document: TextDocument, diagnostic: Diagnostic, proble }); } -function convertSeverity(severity: number | RuleSeverity): DiagnosticSeverity { +function adjustSeverityForOverride(severity: number | RuleSeverity, severityOverride?: RuleSeverity) { + switch (severityOverride) { + case RuleSeverity.info: + case RuleSeverity.warn: + case RuleSeverity.error: + return severityOverride; + + case RuleSeverity.downgrade: + switch (convertSeverityToDiagnostic(severity)) { + case DiagnosticSeverity.Error: + return RuleSeverity.warn; + case DiagnosticSeverity.Warning: + case DiagnosticSeverity.Information: + return RuleSeverity.info; + } + + case RuleSeverity.upgrade: + switch (convertSeverityToDiagnostic(severity)) { + case DiagnosticSeverity.Information: + return RuleSeverity.warn; + case DiagnosticSeverity.Warning: + case DiagnosticSeverity.Error: + return RuleSeverity.error; + } + + default: + return severity; + } +} + +function convertSeverityToDiagnostic(severity: number | RuleSeverity) { // RuleSeverity concerns an overridden rule. A number is direct from ESLint. switch (severity) { // Eslint 1 is warning @@ -536,6 +560,11 @@ function convertSeverity(severity: number | RuleSeverity): DiagnosticSeverity { } } +function convertSeverityToDiagnosticWithOverride(severity: number | RuleSeverity, severityOverride: RuleSeverity | undefined): DiagnosticSeverity { + return convertSeverityToDiagnostic(adjustSeverityForOverride(severity, severityOverride)); + +} + const enum CharCode { /** * The `\` character. @@ -1375,7 +1404,7 @@ function validate(document: TextDocument, settings: TextDocumentSettings & { lib if (docReport.messages && Array.isArray(docReport.messages)) { docReport.messages.forEach((problem) => { if (problem) { - const isWarning = convertSeverity(problem.severity) === DiagnosticSeverity.Warning; + const isWarning = convertSeverityToDiagnostic(problem.severity) === DiagnosticSeverity.Warning; if (settings.quiet && isWarning) { // Filter out warnings when quiet mode is enabled return; From 016e1a0ddb2789c11fb5dcf2d65ac1df908e7e56 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 28 Mar 2021 11:36:20 -0400 Subject: [PATCH 10/12] Update naming to severity, not override/reset --- README.md | 14 +++++++------- client/src/extension.ts | 8 ++++---- package.json | 2 +- playgrounds/6.0/.vscode/settings.json | 10 +++++----- playgrounds/6.0/test.js | 1 + 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 749a1f5c..bfd85b73 100644 --- a/README.md +++ b/README.md @@ -147,15 +147,15 @@ This extension contributes the following variables to the [settings](https://cod - `problems`: fixes only the currently known fixable problems as long as their textual edits are non overlapping. This mode is a lot faster but very likely only fixes parts of the problems. - `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. contain two properties: - - `"rule`": Select on rules with names that match, factoring in asterisks as wildcards: `{ "override": "no-*", "severity": "warn" }` - - Prefix the query with a `"!"` to target all rules that _don't_ match the query: `{ "override": "!no-*", "severity": "info" }` - - `"override"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"reset"`s them to their original severity + - `"rule`": Select on rules with names that match, factoring in asterisks as wildcards: `{ "rule": "no-*", "severity": "warn" }` + - Prefix the query with a `"!"` to target all rules that _don't_ match the query: `{ "rule": "!no-*", "severity": "info" }` + - `"severity"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"reset"`s them to their original severity In this example, all rules are overridden to warnings: ```json "eslint.rules.customizations": [ - { "override": "*", "severity": "warn" } + { "rule": "*", "severity": "warn" } ] ``` @@ -163,9 +163,9 @@ This extension contributes the following variables to the [settings](https://cod ```json "eslint.rules.customizations": [ - { "override": "no-*", "severity": "info" }, - { "override": "!no-*", "severity": "downgrade" }, - { "reset": "radix", "severity": "reset" } + { "rule": "no-*", "severity": "info" }, + { "rule": "!no-*", "severity": "downgrade" }, + { "rule": "radix", "severity": "reset" } ] ``` diff --git a/client/src/extension.ts b/client/src/extension.ts index cfc5b797..64be0240 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -187,7 +187,7 @@ enum RuleSeverity { warn = 'warn', error = 'error', - // Added override changes + // Added severity override changes reset = 'reset', downgrade = 'downgrade', upgrade = 'upgrade' @@ -195,7 +195,7 @@ enum RuleSeverity { interface RuleCustomization { rule: string; - override: RuleSeverity; + severity: RuleSeverity; } interface ConfigurationSettings { @@ -423,9 +423,9 @@ function parseRulesCustomizations(rawConfig: unknown): RuleCustomization[] { } return rawConfig.map(rawValue => { - if ('override' in rawValue && typeof rawValue.override === 'string' && 'rule' in rawValue && typeof rawValue.rule === 'string') { + if ('severity' in rawValue && typeof rawValue.severity === 'string' && 'rule' in rawValue && typeof rawValue.rule === 'string') { return { - override: rawValue.override, + severity: rawValue.severity, rule: rawValue.rule, }; } diff --git a/package.json b/package.json index 182c7af7..3aaea061 100644 --- a/package.json +++ b/package.json @@ -380,7 +380,7 @@ "description": "Override the severity of one or more rules reported by this extension, regardless of the project's ESLint config. Use globs to apply default severities for multiple rules.", "items": { "properties": { - "override": { + "severity": { "enum": [ "downgrade", "error", diff --git a/playgrounds/6.0/.vscode/settings.json b/playgrounds/6.0/.vscode/settings.json index 5bd2a299..e6be1317 100644 --- a/playgrounds/6.0/.vscode/settings.json +++ b/playgrounds/6.0/.vscode/settings.json @@ -40,11 +40,11 @@ }, "eslint.quiet": false, "eslint.rules.customizations": [ - { "rule": "*", "override": "info" }, - { "rule": "!no-*", "override": "downgrade" }, - { "rule": "*console*", "override": "upgrade" }, - { "rule": "*semi*", "override": "reset" }, - { "rule": "radix", "override": "reset" } + { "rule": "*", "severity": "error" }, + { "rule": "!no-*", "severity": "upgrade" }, + { "rule": "*console*", "severity": "upgrade" }, + { "rule": "*semi*", "severity": "reset" }, + { "rule": "radix", "severity": "reset" } ], "editor.codeActionsOnSaveTimeout": 2000, "eslint.format.enable": true, diff --git a/playgrounds/6.0/test.js b/playgrounds/6.0/test.js index 69349b73..6ee107bf 100644 --- a/playgrounds/6.0/test.js +++ b/playgrounds/6.0/test.js @@ -3,6 +3,7 @@ function bar() { let str = 'hallo'; foo(str); } + console.log("aha!"); } function foo(str) { From 087bd29339d1d6ea8922206ec93fb38a5640062d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 29 Mar 2021 05:50:22 -0400 Subject: [PATCH 11/12] Server severity rename, and reset -> default --- README.md | 6 +++--- client/src/extension.ts | 2 +- package.json | 2 +- playgrounds/6.0/.vscode/settings.json | 4 ++-- server/src/eslintServer.ts | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bfd85b73..08bbc628 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ This extension contributes the following variables to the [settings](https://cod - `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. contain two properties: - `"rule`": Select on rules with names that match, factoring in asterisks as wildcards: `{ "rule": "no-*", "severity": "warn" }` - Prefix the query with a `"!"` to target all rules that _don't_ match the query: `{ "rule": "!no-*", "severity": "info" }` - - `"severity"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"reset"`s them to their original severity + - `"severity"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"default"`s them to their original severity In this example, all rules are overridden to warnings: @@ -159,13 +159,13 @@ This extension contributes the following variables to the [settings](https://cod ] ``` - In this example, `no-` rules are informative, other rules are downgraded, and `"radix"` is reset: + In this example, `no-` rules are informative, other rules are downgraded, and `"radix"` is reset to default: ```json "eslint.rules.customizations": [ { "rule": "no-*", "severity": "info" }, { "rule": "!no-*", "severity": "downgrade" }, - { "rule": "radix", "severity": "reset" } + { "rule": "radix", "severity": "default" } ] ``` diff --git a/client/src/extension.ts b/client/src/extension.ts index 64be0240..1f60742e 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -188,7 +188,7 @@ enum RuleSeverity { error = 'error', // Added severity override changes - reset = 'reset', + default = 'default', downgrade = 'downgrade', upgrade = 'upgrade' } diff --git a/package.json b/package.json index 3aaea061..4ae8e5b4 100644 --- a/package.json +++ b/package.json @@ -385,7 +385,7 @@ "downgrade", "error", "info", - "reset", + "default", "upgrade", "warn" ], diff --git a/playgrounds/6.0/.vscode/settings.json b/playgrounds/6.0/.vscode/settings.json index e6be1317..c1e304a5 100644 --- a/playgrounds/6.0/.vscode/settings.json +++ b/playgrounds/6.0/.vscode/settings.json @@ -43,8 +43,8 @@ { "rule": "*", "severity": "error" }, { "rule": "!no-*", "severity": "upgrade" }, { "rule": "*console*", "severity": "upgrade" }, - { "rule": "*semi*", "severity": "reset" }, - { "rule": "radix", "severity": "reset" } + { "rule": "*semi*", "severity": "default" }, + { "rule": "radix", "severity": "default" } ], "editor.codeActionsOnSaveTimeout": 2000, "eslint.format.enable": true, diff --git a/server/src/eslintServer.ts b/server/src/eslintServer.ts index f928e9c0..b4b42dc5 100644 --- a/server/src/eslintServer.ts +++ b/server/src/eslintServer.ts @@ -231,16 +231,16 @@ enum RuleSeverity { warn = 'warn', error = 'error', - // Added override changes + // Added severity override changes off = 'off', - reset = 'reset', + default = 'default', downgrade = 'downgrade', upgrade = 'upgrade' } interface RuleCustomization { rule: string; - override: RuleSeverity; + severity: RuleSeverity; } interface CommonSettings { @@ -406,7 +406,7 @@ function getSeverityOverride(ruleId: string, customizations: RuleCustomization[] for (const customization of customizations) { if (asteriskMatches(customization.rule, ruleId)) { - result = customization.override; + result = customization.severity; } } From 438a5a111bb657b9b83911cfc5059bb97c0ecdc3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 1 Apr 2021 09:09:57 -0400 Subject: [PATCH 12/12] Review touchups --- README.md | 4 ++-- client/src/extension.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 08bbc628..804f5c0e 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,9 @@ This extension contributes the following variables to the [settings](https://cod - `all`: fixes all possible problems by revalidating the file's content. This executes the same code path as running eslint with the `--fix` option in the terminal and therefore can take some time. This is the default value. - `problems`: fixes only the currently known fixable problems as long as their textual edits are non overlapping. This mode is a lot faster but very likely only fixes parts of the problems. -- `eslint.rules.customizations`: force rules to report a different severity within VS Code compared to the project's true ESLint configuration. contain two properties: +- `eslint.rules.customizations` (@since 2.1.20): force rules to report a different severity within VS Code compared to the project's true ESLint configuration. Contains two properties: - `"rule`": Select on rules with names that match, factoring in asterisks as wildcards: `{ "rule": "no-*", "severity": "warn" }` - - Prefix the query with a `"!"` to target all rules that _don't_ match the query: `{ "rule": "!no-*", "severity": "info" }` + - Prefix the name with a `"!"` to target all rules that _don't_ match the name: `{ "rule": "!no-*", "severity": "info" }` - `"severity"`: Sets a new severity for matched rule(s), `"downgrade"`s them to a lower severity, `"upgrade"`s them to a higher severity, or `"default"`s them to their original severity In this example, all rules are overridden to warnings: diff --git a/client/src/extension.ts b/client/src/extension.ts index 1f60742e..846d1949 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -423,7 +423,7 @@ function parseRulesCustomizations(rawConfig: unknown): RuleCustomization[] { } return rawConfig.map(rawValue => { - if ('severity' in rawValue && typeof rawValue.severity === 'string' && 'rule' in rawValue && typeof rawValue.rule === 'string') { + if (typeof rawValue.severity === 'string' && typeof rawValue.rule === 'string') { return { severity: rawValue.severity, rule: rawValue.rule,