Skip to content

Commit c324053

Browse files
committed
simplify hint diagnostics into a single separate diagnostic category and report all baselined diagnostics with it
1 parent 4eeb4ef commit c324053

22 files changed

+244
-357
lines changed

docs/configuration.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,23 @@ The following settings determine how different types should be evaluated.
7171

7272
- <a name="disableBytesTypePromotions"></a> **disableBytesTypePromotions** [boolean]: Disables legacy behavior where `bytearray` and `memoryview` are considered subtypes of `bytes`. [PEP 688](https://peps.python.org/pep-0688/#no-special-meaning-for-bytes) deprecates this behavior, but this switch is provided to restore the older behavior.
7373

74+
## Diagnostic Categories
75+
76+
diagnostics can be configured to be reported as any of the following categories:
77+
78+
- `"error"` - causes the CLI to fail with exit code 1
79+
- `"warning"` - only causes the CLI to fail if [`failOnWarnings`](#failOnWarnings) is enabled or the [`--warnings`](./command-line.md#command-line) argument is used
80+
- `"information"` - never causes the CLI to fail
81+
- `"hint"` - only appears as a hint in the language server, not reported in the CLI at all. [baselined diagnostics](../benefits-over-pyright/baseline.md) are reported as hints
82+
83+
!!! note
84+
the `"unreachable"`, `"unused"` and `"deprecated"` diagnostic categories are deprecated in favor of `"hint"`. rules where it makes sense
85+
to be report them as "unnecessary" or "deprecated" [as mentioned in the LSP spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity) are still reported as such, the configuration to do so has just been simplified.
86+
87+
the `"hint"` diagnostic category is more flexible as it can be used on rules that don't refer to something that's unused, unreachable or deprecated. [baselined diagnostics](../benefits-over-pyright/baseline.md) are now all reported as a hint, instead of just the ones that supported one of the specific diagnostic tag categories.
88+
89+
for backwards compatibility, setting a diagnostic rule to any of these three deprecated categories will act as an alias for the `"hint"` category, however they may be removed entirely in a future release.
90+
7491
## Type Check Diagnostics Settings
7592
The following settings control pyright’s diagnostic output (warnings or errors).
7693

@@ -80,7 +97,7 @@ The following settings control pyright’s diagnostic output (warnings or errors
8097

8198
### Type Check Rule Overrides
8299

83-
The following settings allow more fine grained control over the **typeCheckingMode**. Unless otherwise specified, each diagnostic setting can specify a boolean value (`false` indicating that no error is generated and `true` indicating that an error is generated). Alternatively, a string value of `"none"`, `"warning"`, `"information"`, or `"error"` can be used to specify the diagnostic level.
100+
The following settings allow more fine grained control over the **typeCheckingMode**. Unless otherwise specified, each diagnostic setting can specify a boolean value (`false` indicating that no error is generated and `true` indicating that an error is generated). Alternatively, a string value of `"none"`, `"hint"`, `"warning"`, `"information"`, or `"error"` can be used to specify the diagnostic level. [see above for more information](#diagnostic-categories)
84101

85102
- <a name="reportGeneralTypeIssues"></a> **reportGeneralTypeIssues** [boolean or string, optional]: Generate or suppress diagnostics for general type inconsistencies, unsupported operations, argument/parameter mismatches, etc. This covers all of the basic type-checking rules not covered by other rules. It does not include syntax errors.
86103

@@ -387,11 +404,10 @@ executionEnvironments = [
387404

388405
Each diagnostic setting has a default that is dictated by the specified type checking mode. The default for each rule can be overridden in the configuration file or settings.
389406

390-
Some rules have an additional severity level such as `"unused"`, `"deprecated"` or `"unreachable"`. These are only used by the language server so that your editor can grey out or add a strikethrough to the symbol, which you can disable by setting it to `"off"`. it does not effect the outcome when running basedpyright via the CLI, so in that context these severity levels essentially mean the same thing as `"off"`.
407+
Some rules default to `"hint"`. This diagnostic category is only used by the language server so that your editor can grey out or add a strikethrough to the symbol, which you can disable by setting it to `"off"`. it does not effect the outcome when running basedpyright via the CLI, so in that context these severity levels essentially mean the same thing as `"off"`. [see here](#diagnostic-categories) for more information about each diagnostic category.
391408

392409
The following table lists the default severity levels for each diagnostic rule within each type checking mode (`"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"` and `"all"`).
393410

394-
395411
### `"recommended"` and `"all"`
396412

397413
basedpyright introduces two new diagnostic rulesets in addition to the ones in pyright: `"recommended"` and `"all"`. `"recommended"` enables all diagnostic rules as either `"warning"` or `"error"`, but sets `failOnWarnings` to `true` so that all diagnostics will still cause a non-zero exit code when run in the CLI. this means `"recommended"` is essentially the same as `"all"`, but makes it easier to differentiate errors that are likely to cause a runtime crash like an undefined variable from less serious warnings such as a missing type annotation.

packages/pyright-internal/src/analyzer/binder.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import { Commands } from '../commands/commands';
2020
import { appendArray } from '../common/collectionUtils';
21-
import { LspDiagnosticLevel } from '../common/configOptions';
21+
import { DiagnosticLevel } from '../common/configOptions';
2222
import { assert, assertNever, fail } from '../common/debug';
2323
import { CreateTypeStubFileAction, Diagnostic, DiagnosticAddendum } from '../common/diagnostic';
2424
import { DiagnosticRule } from '../common/diagnosticRules';
@@ -4283,16 +4283,14 @@ export class Binder extends ParseTreeWalker {
42834283
}
42844284

42854285
private _addDiagnostic(rule: DiagnosticRule, message: string, textRange: TextRange) {
4286-
const diagLevel = this._fileInfo.diagnosticRuleSet[rule] as LspDiagnosticLevel;
4286+
const diagLevel = this._fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel;
42874287

42884288
let diagnostic: Diagnostic | undefined;
42894289
switch (diagLevel) {
42904290
case 'error':
42914291
case 'warning':
42924292
case 'information':
4293-
case 'unreachable':
4294-
case 'unused':
4295-
case 'deprecated':
4293+
case 'hint':
42964294
diagnostic = this._fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, textRange);
42974295
break;
42984296

packages/pyright-internal/src/analyzer/program.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -894,12 +894,7 @@ export class Program {
894894
if (diagnostics !== undefined) {
895895
// Filter out all categories that are translated to tagged hints?
896896
if (options.disableTaggedHints) {
897-
diagnostics = diagnostics.filter(
898-
(diag) =>
899-
diag.category !== DiagnosticCategory.UnreachableCode &&
900-
diag.category !== DiagnosticCategory.UnusedCode &&
901-
diag.category !== DiagnosticCategory.Deprecated
902-
);
897+
diagnostics = diagnostics.filter((diag) => diag.category !== DiagnosticCategory.Hint);
903898
}
904899

905900
fileDiagnostics.push({

packages/pyright-internal/src/analyzer/sourceFile.ts

+20-21
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import { isMainThread } from '../common/workersHost';
1111

1212
import { OperationCanceledException } from '../common/cancellationUtils';
1313
import { appendArray } from '../common/collectionUtils';
14-
import { ConfigOptions, ExecutionEnvironment, getBasicDiagnosticRuleSet } from '../common/configOptions';
14+
import {
15+
ConfigOptions,
16+
ExecutionEnvironment,
17+
getBasicDiagnosticRuleSet,
18+
unreachableDiagnosticRules,
19+
} from '../common/configOptions';
1520
import { ConsoleInterface, StandardConsole } from '../common/console';
1621
import { assert } from '../common/debug';
1722
import { Diagnostic, DiagnosticCategory, TaskListToken, convertLevelToCategory } from '../common/diagnostic';
@@ -1018,11 +1023,7 @@ export class SourceFile {
10181023
if (this._diagnosticRuleSet.enableTypeIgnoreComments) {
10191024
if (this._writableData.typeIgnoreLines.size > 0) {
10201025
diagList = diagList.filter((d) => {
1021-
if (
1022-
d.category !== DiagnosticCategory.UnusedCode &&
1023-
d.category !== DiagnosticCategory.UnreachableCode &&
1024-
d.category !== DiagnosticCategory.Deprecated
1025-
) {
1026+
if (d.category !== DiagnosticCategory.Hint) {
10261027
for (let line = d.range.start.line; line <= d.range.end.line; line++) {
10271028
if (this._writableData.typeIgnoreLines.has(line)) {
10281029
typeIgnoreLinesClone.delete(line);
@@ -1039,11 +1040,7 @@ export class SourceFile {
10391040
// Filter the diagnostics based on "pyright: ignore" lines.
10401041
if (this._writableData.pyrightIgnoreLines.size > 0) {
10411042
diagList = diagList.filter((d) => {
1042-
if (
1043-
d.category !== DiagnosticCategory.UnusedCode &&
1044-
d.category !== DiagnosticCategory.UnreachableCode &&
1045-
d.category !== DiagnosticCategory.Deprecated
1046-
) {
1043+
if (d.category !== DiagnosticCategory.Hint) {
10471044
for (let line = d.range.start.line; line <= d.range.end.line; line++) {
10481045
const pyrightIgnoreComment = this._writableData.pyrightIgnoreLines.get(line);
10491046
if (pyrightIgnoreComment) {
@@ -1137,13 +1134,20 @@ export class SourceFile {
11371134
diag.category === DiagnosticCategory.Information
11381135
);
11391136

1137+
const unreachableDiagnostics = unreachableDiagnosticRules();
11401138
const isUnreachableCodeRange = (range: Range) => {
1141-
return prefilteredDiagList.find(
1142-
(diag) =>
1143-
diag.category === DiagnosticCategory.UnreachableCode &&
1139+
return prefilteredDiagList.find((diag) => {
1140+
if (diag.category !== DiagnosticCategory.Hint) {
1141+
return false;
1142+
}
1143+
const rule = diag.getRule();
1144+
return (
1145+
rule &&
1146+
unreachableDiagnostics.includes(rule as DiagnosticRule) &&
11441147
diag.range.start.line <= range.start.line &&
11451148
diag.range.end.line >= range.end.line
1146-
);
1149+
);
1150+
});
11471151
};
11481152

11491153
if (prefilteredErrorList.length === 0 && this._writableData.typeIgnoreAll !== undefined) {
@@ -1256,12 +1260,7 @@ export class SourceFile {
12561260
// the errors and warnings, leaving only the unreachable code
12571261
// and deprecated diagnostics.
12581262
if (!includeWarningsAndErrors) {
1259-
diagList = diagList.filter(
1260-
(diag) =>
1261-
diag.category === DiagnosticCategory.UnusedCode ||
1262-
diag.category === DiagnosticCategory.UnreachableCode ||
1263-
diag.category === DiagnosticCategory.Deprecated
1264-
);
1263+
diagList = diagList.filter((diag) => diag.category === DiagnosticCategory.Hint);
12651264
}
12661265

12671266
// If the file is in the ignore list, clear the diagnostic list.

packages/pyright-internal/src/analyzer/typeEvaluator.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { CancellationToken } from 'vscode-languageserver';
1818

1919
import { invalidateTypeCacheIfCanceled, throwIfCancellationRequested } from '../common/cancellationUtils';
2020
import { appendArray } from '../common/collectionUtils';
21-
import { DiagnosticRuleSet, LspDiagnosticLevel } from '../common/configOptions';
21+
import { DiagnosticRuleSet, DiagnosticLevel } from '../common/configOptions';
2222
import { ConsoleInterface } from '../common/console';
2323
import { assert, assertNever, fail } from '../common/debug';
2424
import { DiagnosticAddendum } from '../common/diagnostic';
@@ -3395,7 +3395,7 @@ export function createTypeEvaluator(
33953395
}
33963396

33973397
function addDiagnosticWithSuppressionCheck(
3398-
diagLevel: LspDiagnosticLevel,
3398+
diagLevel: DiagnosticLevel,
33993399
message: string,
34003400
node: ParseNode,
34013401
range?: TextRange,
@@ -3450,7 +3450,7 @@ export function createTypeEvaluator(
34503450

34513451
function addDiagnostic(rule: DiagnosticRule, message: string, node: ParseNode, range?: TextRange) {
34523452
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
3453-
const diagLevel = fileInfo.diagnosticRuleSet[rule] as LspDiagnosticLevel;
3453+
const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel;
34543454

34553455
if (diagLevel === 'none') {
34563456
return undefined;
@@ -3504,7 +3504,7 @@ export function createTypeEvaluator(
35043504
message: string,
35053505
range: TextRange
35063506
) {
3507-
const diagLevel = fileInfo.diagnosticRuleSet[rule] as LspDiagnosticLevel;
3507+
const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel;
35083508

35093509
if (diagLevel === 'none') {
35103510
return undefined;

packages/pyright-internal/src/baseline.ts

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { DiagnosticRule } from './common/diagnosticRules';
22
import { FileDiagnostics } from './common/diagnosticSink';
33
import { Uri } from './common/uri/uri';
4-
import { compareDiagnostics, convertLevelToCategory, Diagnostic, isHintDiagnostic } from './common/diagnostic';
5-
import { extraOptionDiagnosticRules } from './common/configOptions';
4+
import { compareDiagnostics, Diagnostic, DiagnosticCategory } from './common/diagnostic';
65
import { fileExists } from './common/uri/uriUtils';
76
import { FileSystem } from './common/fileSystem';
87
import { pluralize } from './common/stringUtils';
@@ -126,7 +125,7 @@ export class BaselineHandler {
126125
const newDiagnostics = filesWithDiagnostics.map((file) => ({
127126
...file,
128127
diagnostics: file.diagnostics.filter(
129-
(diagnostic) => !diagnostic.baselineStatus && !isHintDiagnostic(diagnostic)
128+
(diagnostic) => !diagnostic.baselined && diagnostic.category !== DiagnosticCategory.Hint
130129
),
131130
}));
132131
if (newDiagnostics.map((fileWithDiagnostics) => fileWithDiagnostics.diagnostics.length).reduce(add, 0)) {
@@ -188,10 +187,11 @@ export class BaselineHandler {
188187
assert(change.value[0] instanceof Diagnostic, "change object wasn't a Diagnostic");
189188
result.push(...(change.value as Diagnostic[]));
190189
} else {
191-
// if not added and not removed
190+
// if unchanged
191+
192192
// if the baselined error can be reported as a hint (eg. unreachable/deprecated), keep it and change its diagnostic
193193
// level to that instead
194-
// TODO: should we only baseline errors and not warnings/notes?
194+
// TODO: should we only baseline errors/warnings and not notes?
195195
for (const diagnostic of change.value) {
196196
assert(
197197
diagnostic instanceof Diagnostic,
@@ -200,20 +200,14 @@ export class BaselineHandler {
200200
let newDiagnostic;
201201
const diagnosticRule = diagnostic.getRule() as DiagnosticRule | undefined;
202202
if (diagnosticRule) {
203-
for (const { name, get } of extraOptionDiagnosticRules) {
204-
if (get().includes(diagnosticRule)) {
205-
newDiagnostic = diagnostic.copy({
206-
category: convertLevelToCategory(name),
207-
baselineStatus: 'baselined with hint',
208-
});
209-
newDiagnostic.setRule(diagnosticRule);
210-
// none of these rules should have multiple extra diagnostic levels so we break after the first match
211-
break;
212-
}
213-
}
203+
newDiagnostic = diagnostic.copy({
204+
category: DiagnosticCategory.Hint,
205+
baselined: true,
206+
});
207+
newDiagnostic.setRule(diagnosticRule);
214208
}
215209
if (!newDiagnostic) {
216-
newDiagnostic = diagnostic.copy({ baselineStatus: 'baselined' });
210+
newDiagnostic = diagnostic.copy({ baselined: true });
217211
}
218212
result.push(newDiagnostic);
219213
}
@@ -224,12 +218,12 @@ export class BaselineHandler {
224218

225219
/**
226220
* filters out diagnostics that are baselined, but keeps any that have been turned into hints. so you will need
227-
* to filter it further using {@link isHintDiagnostic} if you want those removed as well
221+
* to filter it further by removing diagnostics with {@link DiagnosticCategory.Hint} if you want those removed as well
228222
*/
229223
filterOutBaselinedDiagnostics = (filesWithDiagnostics: readonly FileDiagnostics[]): readonly FileDiagnostics[] =>
230224
filesWithDiagnostics.map((file) => ({
231225
...file,
232-
diagnostics: file.diagnostics.filter((diagnostic) => diagnostic.baselineStatus !== 'baselined'),
226+
diagnostics: file.diagnostics.filter((diagnostic) => !diagnostic.baselined),
233227
}));
234228

235229
private _getBaselinedErrorsForFile = (file: Uri): BaselinedDiagnostic[] => {
@@ -249,7 +243,7 @@ export class BaselineHandler {
249243
for (const fileWithDiagnostics of filesWithDiagnostics) {
250244
const filePath = this._rootDir.getRelativePath(fileWithDiagnostics.fileUri)!.toString();
251245
const errorDiagnostics = fileWithDiagnostics.diagnostics.filter(
252-
(diagnostic) => !isHintDiagnostic(diagnostic) || diagnostic.baselineStatus === 'baselined with hint'
246+
(diagnostic) => diagnostic.category !== DiagnosticCategory.Hint || diagnostic.baselined
253247
);
254248
if (!(filePath in baselineData.files)) {
255249
baselineData.files[filePath] = [];

packages/pyright-internal/src/common/commandLineOptions.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ export const enum DiagnosticSeverityOverrides {
1818
Warning = 'warning',
1919
Information = 'information',
2020
None = 'none',
21-
Unused = 'unused',
22-
Unreachable = 'unreachable',
23-
Deprecated = 'deprecated',
21+
Hint = 'hint',
2422
}
2523

2624
export function getDiagnosticSeverityOverrides() {
@@ -29,9 +27,7 @@ export function getDiagnosticSeverityOverrides() {
2927
DiagnosticSeverityOverrides.Warning,
3028
DiagnosticSeverityOverrides.Information,
3129
DiagnosticSeverityOverrides.None,
32-
DiagnosticSeverityOverrides.Unused,
33-
DiagnosticSeverityOverrides.Unreachable,
34-
DiagnosticSeverityOverrides.Deprecated,
30+
DiagnosticSeverityOverrides.Hint,
3531
];
3632
}
3733

0 commit comments

Comments
 (0)