mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Added support for per-line suppression of diagnostics using # pyright: ignore
comment. This also supports rule-specific suppression using a list of diagnostic rules, as in # pyright: ignore [reportGeneralTypeIssues]
.
This commit is contained in:
parent
915928f572
commit
4cbd352d39
@ -36,3 +36,10 @@ Diagnostic levels are also supported.
|
|||||||
# pyright: reportPrivateUsage=warning, reportOptionalCall=error
|
# pyright: reportPrivateUsage=warning, reportOptionalCall=error
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Line-level Diagnostic Suppression
|
||||||
|
|
||||||
|
PEP 484 defines a special comment `# type: ignore` that can be used at the end of a line to suppress all diagnostics emitted by a type checker on that line. Pyright supports this mechanism.
|
||||||
|
|
||||||
|
Pyright also supports a `# pyright: ignore` comment at the end of a line to suppress all Pyright diagnostics on that line. This can be useful if you use multiple type checkers on your source base and want to limit suppression of diagnostics to Pyright only.
|
||||||
|
|
||||||
|
The `# pyright: ignore` comment accepts an optional list of comma-delimited diagnostic rule names surrounded by square brackets. If such a list is present, only diagnostics within those diagnostic rule categories are suppressed on that line. For example, `# pyright: ignore [reportPrivateUsage, reportGeneralTypeIssues]` would suppress diagnostics related to those two categories but no others.
|
||||||
|
@ -50,7 +50,7 @@ The following settings control pyright’s diagnostic output (warnings or errors
|
|||||||
|
|
||||||
**strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.
|
**strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.
|
||||||
|
|
||||||
**enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is 'true'.
|
**enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is 'true'. This does not affect "# pyright: ignore" comments.
|
||||||
|
|
||||||
**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. The default value for this setting is 'error'.
|
**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. The default value for this setting is 'error'.
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ The following settings control pyright’s diagnostic output (warnings or errors
|
|||||||
|
|
||||||
**reportUnusedCoroutine** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is a Coroutine. This identifies a common error where an `await` keyword is mistakenly omitted. The default value for this setting is 'error'.
|
**reportUnusedCoroutine** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is a Coroutine. This identifies a common error where an `await` keyword is mistakenly omitted. The default value for this setting is 'error'.
|
||||||
|
|
||||||
**reportUnnecessaryTypeIgnoreComment** [boolean or string, optional]: Generate or suppress diagnostics for a '# type: ignore' comment that would have no effect if removed. The default value for this setting is 'none'.
|
**reportUnnecessaryTypeIgnoreComment** [boolean or string, optional]: Generate or suppress diagnostics for a '# type: ignore' or '# pyright: ignore' comment that would have no effect if removed. The default value for this setting is 'none'.
|
||||||
|
|
||||||
**reportMatchNotExhaustive** [boolean or string, optional]: Generate or suppress diagnostics for a 'match' statement that does not provide cases that exhaustively match against all potential types of the target expression. The default value for this setting is 'none'.
|
**reportMatchNotExhaustive** [boolean or string, optional]: Generate or suppress diagnostics for a 'match' statement that does not provide cases that exhaustively match against all potential types of the target expression. The default value for this setting is 'none'.
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import { SignatureHelpProvider, SignatureHelpResults } from '../languageService/
|
|||||||
import { Localizer } from '../localization/localize';
|
import { Localizer } from '../localization/localize';
|
||||||
import { ModuleNode, NameNode } from '../parser/parseNodes';
|
import { ModuleNode, NameNode } from '../parser/parseNodes';
|
||||||
import { ModuleImport, ParseOptions, Parser, ParseResults } from '../parser/parser';
|
import { ModuleImport, ParseOptions, Parser, ParseResults } from '../parser/parser';
|
||||||
|
import { IgnoreComment } from '../parser/tokenizer';
|
||||||
import { Token } from '../parser/tokenizerTypes';
|
import { Token } from '../parser/tokenizerTypes';
|
||||||
import { AnalyzerFileInfo, ImportLookup } from './analyzerFileInfo';
|
import { AnalyzerFileInfo, ImportLookup } from './analyzerFileInfo';
|
||||||
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||||
@ -146,8 +147,9 @@ export class SourceFile {
|
|||||||
private _parseDiagnostics: Diagnostic[] = [];
|
private _parseDiagnostics: Diagnostic[] = [];
|
||||||
private _bindDiagnostics: Diagnostic[] = [];
|
private _bindDiagnostics: Diagnostic[] = [];
|
||||||
private _checkerDiagnostics: Diagnostic[] = [];
|
private _checkerDiagnostics: Diagnostic[] = [];
|
||||||
private _typeIgnoreLines = new Map<number, TextRange>();
|
private _typeIgnoreLines = new Map<number, IgnoreComment>();
|
||||||
private _typeIgnoreAll: TextRange | undefined;
|
private _typeIgnoreAll: IgnoreComment | undefined;
|
||||||
|
private _pyrightIgnoreLines = new Map<number, IgnoreComment>();
|
||||||
|
|
||||||
// Settings that control which diagnostics should be output.
|
// Settings that control which diagnostics should be output.
|
||||||
private _diagnosticRuleSet = getBasicDiagnosticRuleSet();
|
private _diagnosticRuleSet = getBasicDiagnosticRuleSet();
|
||||||
@ -259,6 +261,7 @@ export class SourceFile {
|
|||||||
let diagList = [...this._parseDiagnostics, ...this._bindDiagnostics, ...this._checkerDiagnostics];
|
let diagList = [...this._parseDiagnostics, ...this._bindDiagnostics, ...this._checkerDiagnostics];
|
||||||
const prefilteredDiagList = diagList;
|
const prefilteredDiagList = diagList;
|
||||||
const typeIgnoreLinesClone = new Map(this._typeIgnoreLines);
|
const typeIgnoreLinesClone = new Map(this._typeIgnoreLines);
|
||||||
|
const pyrightIgnoreLinesClone = new Map(this._pyrightIgnoreLines);
|
||||||
|
|
||||||
// Filter the diagnostics based on "type: ignore" lines.
|
// Filter the diagnostics based on "type: ignore" lines.
|
||||||
if (this._diagnosticRuleSet.enableTypeIgnoreComments) {
|
if (this._diagnosticRuleSet.enableTypeIgnoreComments) {
|
||||||
@ -278,6 +281,55 @@ export class SourceFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter the diagnostics based on "pyright: ignore" lines.
|
||||||
|
if (this._pyrightIgnoreLines.size > 0) {
|
||||||
|
diagList = diagList.filter((d) => {
|
||||||
|
if (d.category !== DiagnosticCategory.UnusedCode && d.category !== DiagnosticCategory.Deprecated) {
|
||||||
|
for (let line = d.range.start.line; line <= d.range.end.line; line++) {
|
||||||
|
const pyrightIgnoreComment = this._pyrightIgnoreLines.get(line);
|
||||||
|
if (pyrightIgnoreComment) {
|
||||||
|
if (!pyrightIgnoreComment.rulesList) {
|
||||||
|
pyrightIgnoreLinesClone.delete(line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diagRule = d.getRule();
|
||||||
|
if (!diagRule) {
|
||||||
|
// If there's no diagnostic rule, it won't match
|
||||||
|
// against a rules list.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did we find this rule in the list?
|
||||||
|
if (pyrightIgnoreComment.rulesList.find((rule) => rule.text === diagRule)) {
|
||||||
|
// Update the pyrightIgnoreLinesClone to remove this rule.
|
||||||
|
const oldClone = pyrightIgnoreLinesClone.get(line);
|
||||||
|
if (oldClone?.rulesList) {
|
||||||
|
const filteredRulesList = oldClone.rulesList.filter(
|
||||||
|
(rule) => rule.text !== diagRule
|
||||||
|
);
|
||||||
|
if (filteredRulesList.length === 0) {
|
||||||
|
pyrightIgnoreLinesClone.delete(line);
|
||||||
|
} else {
|
||||||
|
pyrightIgnoreLinesClone.set(line, {
|
||||||
|
range: oldClone.range,
|
||||||
|
rulesList: filteredRulesList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const unnecessaryTypeIgnoreDiags: Diagnostic[] = [];
|
const unnecessaryTypeIgnoreDiags: Diagnostic[] = [];
|
||||||
|
|
||||||
if (this._diagnosticRuleSet.reportUnnecessaryTypeIgnoreComment !== 'none') {
|
if (this._diagnosticRuleSet.reportUnnecessaryTypeIgnoreComment !== 'none') {
|
||||||
@ -296,29 +348,63 @@ export class SourceFile {
|
|||||||
diagCategory,
|
diagCategory,
|
||||||
Localizer.Diagnostic.unnecessaryTypeIgnore(),
|
Localizer.Diagnostic.unnecessaryTypeIgnore(),
|
||||||
convertOffsetsToRange(
|
convertOffsetsToRange(
|
||||||
this._typeIgnoreAll.start,
|
this._typeIgnoreAll.range.start,
|
||||||
this._typeIgnoreAll.start + this._typeIgnoreAll.length,
|
this._typeIgnoreAll.range.start + this._typeIgnoreAll.range.length,
|
||||||
this._parseResults!.tokenizerOutput.lines!
|
this._parseResults!.tokenizerOutput.lines!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
typeIgnoreLinesClone.forEach((textRange) => {
|
typeIgnoreLinesClone.forEach((ignoreComment) => {
|
||||||
if (this._parseResults?.tokenizerOutput.lines) {
|
if (this._parseResults?.tokenizerOutput.lines) {
|
||||||
unnecessaryTypeIgnoreDiags.push(
|
unnecessaryTypeIgnoreDiags.push(
|
||||||
new Diagnostic(
|
new Diagnostic(
|
||||||
diagCategory,
|
diagCategory,
|
||||||
Localizer.Diagnostic.unnecessaryTypeIgnore(),
|
Localizer.Diagnostic.unnecessaryTypeIgnore(),
|
||||||
convertOffsetsToRange(
|
convertOffsetsToRange(
|
||||||
textRange.start,
|
ignoreComment.range.start,
|
||||||
textRange.start + textRange.length,
|
ignoreComment.range.start + ignoreComment.range.length,
|
||||||
this._parseResults!.tokenizerOutput.lines!
|
this._parseResults!.tokenizerOutput.lines!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pyrightIgnoreLinesClone.forEach((ignoreComment) => {
|
||||||
|
if (this._parseResults?.tokenizerOutput.lines) {
|
||||||
|
if (!ignoreComment.rulesList) {
|
||||||
|
unnecessaryTypeIgnoreDiags.push(
|
||||||
|
new Diagnostic(
|
||||||
|
diagCategory,
|
||||||
|
Localizer.Diagnostic.unnecessaryPyrightIgnore(),
|
||||||
|
convertOffsetsToRange(
|
||||||
|
ignoreComment.range.start,
|
||||||
|
ignoreComment.range.start + ignoreComment.range.length,
|
||||||
|
this._parseResults!.tokenizerOutput.lines!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ignoreComment.rulesList.forEach((unusedRule) => {
|
||||||
|
unnecessaryTypeIgnoreDiags.push(
|
||||||
|
new Diagnostic(
|
||||||
|
diagCategory,
|
||||||
|
Localizer.Diagnostic.unnecessaryPyrightIgnoreRule().format({
|
||||||
|
name: unusedRule.text,
|
||||||
|
}),
|
||||||
|
convertOffsetsToRange(
|
||||||
|
unusedRule.range.start,
|
||||||
|
unusedRule.range.start + unusedRule.range.length,
|
||||||
|
this._parseResults!.tokenizerOutput.lines!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._diagnosticRuleSet.reportImportCycles !== 'none' && this._circularDependencies.length > 0) {
|
if (this._diagnosticRuleSet.reportImportCycles !== 'none' && this._circularDependencies.length > 0) {
|
||||||
@ -659,6 +745,7 @@ export class SourceFile {
|
|||||||
this._parseResults = parseResults;
|
this._parseResults = parseResults;
|
||||||
this._typeIgnoreLines = this._parseResults.tokenizerOutput.typeIgnoreLines;
|
this._typeIgnoreLines = this._parseResults.tokenizerOutput.typeIgnoreLines;
|
||||||
this._typeIgnoreAll = this._parseResults.tokenizerOutput.typeIgnoreAll;
|
this._typeIgnoreAll = this._parseResults.tokenizerOutput.typeIgnoreAll;
|
||||||
|
this._pyrightIgnoreLines = this._parseResults.tokenizerOutput.pyrightIgnoreLines;
|
||||||
|
|
||||||
// Resolve imports.
|
// Resolve imports.
|
||||||
timingStats.resolveImportsTime.timeOperation(() => {
|
timingStats.resolveImportsTime.timeOperation(() => {
|
||||||
@ -704,7 +791,8 @@ export class SourceFile {
|
|||||||
tokens: new TextRangeCollection<Token>([]),
|
tokens: new TextRangeCollection<Token>([]),
|
||||||
lines: new TextRangeCollection<TextRange>([]),
|
lines: new TextRangeCollection<TextRange>([]),
|
||||||
typeIgnoreAll: undefined,
|
typeIgnoreAll: undefined,
|
||||||
typeIgnoreLines: new Map<number, TextRange>(),
|
typeIgnoreLines: new Map<number, IgnoreComment>(),
|
||||||
|
pyrightIgnoreLines: new Map<number, IgnoreComment>(),
|
||||||
predominantEndOfLineSequence: '\n',
|
predominantEndOfLineSequence: '\n',
|
||||||
predominantTabSequence: ' ',
|
predominantTabSequence: ' ',
|
||||||
predominantSingleQuoteCharacter: "'",
|
predominantSingleQuoteCharacter: "'",
|
||||||
|
@ -278,8 +278,8 @@ export interface DiagnosticRuleSet {
|
|||||||
// and is not used in any way.
|
// and is not used in any way.
|
||||||
reportUnusedCoroutine: DiagnosticLevel;
|
reportUnusedCoroutine: DiagnosticLevel;
|
||||||
|
|
||||||
// Report cases where the removal of a "# type: ignore" comment would
|
// Report cases where the removal of a "# type: ignore" or "# pyright: ignore"
|
||||||
// have no effect.
|
// comment would have no effect.
|
||||||
reportUnnecessaryTypeIgnoreComment: DiagnosticLevel;
|
reportUnnecessaryTypeIgnoreComment: DiagnosticLevel;
|
||||||
|
|
||||||
// Report cases where the a "match" statement is not exhaustive in
|
// Report cases where the a "match" statement is not exhaustive in
|
||||||
|
@ -885,6 +885,9 @@ export namespace Localizer {
|
|||||||
new ParameterizedString<{ testType: string; classType: string }>(
|
new ParameterizedString<{ testType: string; classType: string }>(
|
||||||
getRawString('Diagnostic.unnecessaryIsSubclassAlways')
|
getRawString('Diagnostic.unnecessaryIsSubclassAlways')
|
||||||
);
|
);
|
||||||
|
export const unnecessaryPyrightIgnore = () => getRawString('Diagnostic.unnecessaryPyrightIgnore');
|
||||||
|
export const unnecessaryPyrightIgnoreRule = () =>
|
||||||
|
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.unnecessaryPyrightIgnoreRule'));
|
||||||
export const unnecessaryTypeIgnore = () => getRawString('Diagnostic.unnecessaryTypeIgnore');
|
export const unnecessaryTypeIgnore = () => getRawString('Diagnostic.unnecessaryTypeIgnore');
|
||||||
export const unpackArgCount = () => getRawString('Diagnostic.unpackArgCount');
|
export const unpackArgCount = () => getRawString('Diagnostic.unpackArgCount');
|
||||||
export const unpackedArgInTypeArgument = () => getRawString('Diagnostic.unpackedArgInTypeArgument');
|
export const unpackedArgInTypeArgument = () => getRawString('Diagnostic.unpackedArgInTypeArgument');
|
||||||
|
@ -444,7 +444,9 @@
|
|||||||
"unnecessaryCast": "Unnecessary \"cast\" call; type is already \"{type}\"",
|
"unnecessaryCast": "Unnecessary \"cast\" call; type is already \"{type}\"",
|
||||||
"unnecessaryIsInstanceAlways": "Unnecessary isinstance call; \"{testType}\" is always an instance of \"{classType}\"",
|
"unnecessaryIsInstanceAlways": "Unnecessary isinstance call; \"{testType}\" is always an instance of \"{classType}\"",
|
||||||
"unnecessaryIsSubclassAlways": "Unnecessary issubclass call; \"{testType}\" is always a subclass of \"{classType}\"",
|
"unnecessaryIsSubclassAlways": "Unnecessary issubclass call; \"{testType}\" is always a subclass of \"{classType}\"",
|
||||||
"unnecessaryTypeIgnore": "Unnecessary '# type ignore' comment",
|
"unnecessaryPyrightIgnore": "Unnecessary \"# pyright: ignore\" comment",
|
||||||
|
"unnecessaryPyrightIgnoreRule": "Unnecessary \"# pyright: ignore\" rule: \"{name}\"",
|
||||||
|
"unnecessaryTypeIgnore": "Unnecessary \"# type: ignore\" comment",
|
||||||
"unpackArgCount": "Expected a single type argument after \"Unpack\"",
|
"unpackArgCount": "Expected a single type argument after \"Unpack\"",
|
||||||
"unpackedArgInTypeArgument": "Unpacked arguments cannot be used in type argument lists",
|
"unpackedArgInTypeArgument": "Unpacked arguments cannot be used in type argument lists",
|
||||||
"unpackedArgWithVariadicParam": "Unpacked argument cannot be used for TupleTypeVar parameter",
|
"unpackedArgWithVariadicParam": "Unpacked argument cannot be used for TupleTypeVar parameter",
|
||||||
|
@ -146,10 +146,13 @@ export interface TokenizerOutput {
|
|||||||
lines: TextRangeCollection<TextRange>;
|
lines: TextRangeCollection<TextRange>;
|
||||||
|
|
||||||
// Map of all line numbers that end in a "type: ignore" comment.
|
// Map of all line numbers that end in a "type: ignore" comment.
|
||||||
typeIgnoreLines: Map<number, TextRange>;
|
typeIgnoreLines: Map<number, IgnoreComment>;
|
||||||
|
|
||||||
|
// Map of all line numbers that end in a "pyright: ignore" comment.
|
||||||
|
pyrightIgnoreLines: Map<number, IgnoreComment>;
|
||||||
|
|
||||||
// Program starts with a "type: ignore" comment.
|
// Program starts with a "type: ignore" comment.
|
||||||
typeIgnoreAll: TextRange | undefined;
|
typeIgnoreAll: IgnoreComment | undefined;
|
||||||
|
|
||||||
// Line-end sequence ('/n', '/r', or '/r/n').
|
// Line-end sequence ('/n', '/r', or '/r/n').
|
||||||
predominantEndOfLineSequence: string;
|
predominantEndOfLineSequence: string;
|
||||||
@ -174,6 +177,16 @@ interface IndentInfo {
|
|||||||
isTabPresent: boolean;
|
isTabPresent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IgnoreCommentRule {
|
||||||
|
text: string;
|
||||||
|
range: TextRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IgnoreComment {
|
||||||
|
range: TextRange;
|
||||||
|
rulesList: IgnoreCommentRule[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export class Tokenizer {
|
export class Tokenizer {
|
||||||
private _cs = new CharacterStream('');
|
private _cs = new CharacterStream('');
|
||||||
private _tokens: Token[] = [];
|
private _tokens: Token[] = [];
|
||||||
@ -181,8 +194,9 @@ export class Tokenizer {
|
|||||||
private _parenDepth = 0;
|
private _parenDepth = 0;
|
||||||
private _lineRanges: TextRange[] = [];
|
private _lineRanges: TextRange[] = [];
|
||||||
private _indentAmounts: IndentInfo[] = [];
|
private _indentAmounts: IndentInfo[] = [];
|
||||||
private _typeIgnoreAll: TextRange | undefined;
|
private _typeIgnoreAll: IgnoreComment | undefined;
|
||||||
private _typeIgnoreLines = new Map<number, TextRange>();
|
private _typeIgnoreLines = new Map<number, IgnoreComment>();
|
||||||
|
private _pyrightIgnoreLines = new Map<number, IgnoreComment>();
|
||||||
private _comments: Comment[] | undefined;
|
private _comments: Comment[] | undefined;
|
||||||
|
|
||||||
// Total times CR, CR/LF, and LF are used to terminate
|
// Total times CR, CR/LF, and LF are used to terminate
|
||||||
@ -300,6 +314,7 @@ export class Tokenizer {
|
|||||||
lines: new TextRangeCollection(this._lineRanges),
|
lines: new TextRangeCollection(this._lineRanges),
|
||||||
typeIgnoreLines: this._typeIgnoreLines,
|
typeIgnoreLines: this._typeIgnoreLines,
|
||||||
typeIgnoreAll: this._typeIgnoreAll,
|
typeIgnoreAll: this._typeIgnoreAll,
|
||||||
|
pyrightIgnoreLines: this._pyrightIgnoreLines,
|
||||||
predominantEndOfLineSequence,
|
predominantEndOfLineSequence,
|
||||||
predominantTabSequence,
|
predominantTabSequence,
|
||||||
predominantSingleQuoteCharacter: this._singleQuoteCount >= this._doubleQuoteCount ? "'" : '"',
|
predominantSingleQuoteCharacter: this._singleQuoteCount >= this._doubleQuoteCount ? "'" : '"',
|
||||||
@ -1071,27 +1086,62 @@ export class Tokenizer {
|
|||||||
const value = this._cs.getText().substr(start, length);
|
const value = this._cs.getText().substr(start, length);
|
||||||
const comment = Comment.create(start, length, value);
|
const comment = Comment.create(start, length, value);
|
||||||
|
|
||||||
// We include "[" in the regular expression because mypy supports
|
const typeIgnoreRegexMatch = value.match(/^\s*type:\s*ignore(\s*\[([\s*\w-,]*)\]|\s|$)/);
|
||||||
// ignore comments of the form ignore[errorCode, ...]. We'll treat
|
if (typeIgnoreRegexMatch) {
|
||||||
// these as regular ignore statements (as though no errorCodes were
|
const textRange: TextRange = { start, length: typeIgnoreRegexMatch[0].length };
|
||||||
// included).
|
const ignoreComment: IgnoreComment = {
|
||||||
const regexMatch = value.match(/^\s*type:\s*ignore(\s|\[|$)/);
|
range: textRange,
|
||||||
if (regexMatch) {
|
rulesList: this._getIgnoreCommentRulesList(start, typeIgnoreRegexMatch),
|
||||||
const textRange: TextRange = { start, length: regexMatch[0].length };
|
};
|
||||||
if (regexMatch[0].endsWith('[')) {
|
|
||||||
textRange.length--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._tokens.findIndex((t) => t.type !== TokenType.NewLine && t && t.type !== TokenType.Indent) < 0) {
|
if (this._tokens.findIndex((t) => t.type !== TokenType.NewLine && t && t.type !== TokenType.Indent) < 0) {
|
||||||
this._typeIgnoreAll = textRange;
|
this._typeIgnoreAll = ignoreComment;
|
||||||
} else {
|
} else {
|
||||||
this._typeIgnoreLines.set(this._lineRanges.length, textRange);
|
this._typeIgnoreLines.set(this._lineRanges.length, ignoreComment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pyrightIgnoreRegexMatch = value.match(/^\s*pyright:\s*ignore(\s*\[([\s*\w-,]*)\]|\s|$)/);
|
||||||
|
if (pyrightIgnoreRegexMatch) {
|
||||||
|
const textRange: TextRange = { start, length: pyrightIgnoreRegexMatch[0].length };
|
||||||
|
const ignoreComment: IgnoreComment = {
|
||||||
|
range: textRange,
|
||||||
|
rulesList: this._getIgnoreCommentRulesList(start, pyrightIgnoreRegexMatch),
|
||||||
|
};
|
||||||
|
this._pyrightIgnoreLines.set(this._lineRanges.length, ignoreComment);
|
||||||
|
}
|
||||||
|
|
||||||
this._addComments(comment);
|
this._addComments(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extracts the individual rules within a "type: ignore [x, y, z]" comment.
|
||||||
|
private _getIgnoreCommentRulesList(start: number, match: RegExpMatchArray): IgnoreCommentRule[] | undefined {
|
||||||
|
if (match.length < 3 || match[2] === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitElements = match[2].split(',');
|
||||||
|
const commentRules: IgnoreCommentRule[] = [];
|
||||||
|
let currentOffset = start + match[0].indexOf('[') + 1;
|
||||||
|
|
||||||
|
for (const element of splitElements) {
|
||||||
|
const frontTrimmed = element.trimStart();
|
||||||
|
currentOffset += element.length - frontTrimmed.length;
|
||||||
|
const endTrimmed = frontTrimmed.trimEnd();
|
||||||
|
|
||||||
|
if (endTrimmed.length > 0) {
|
||||||
|
commentRules.push({
|
||||||
|
range: { start: currentOffset, length: endTrimmed.length },
|
||||||
|
text: endTrimmed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOffset += frontTrimmed.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentRules;
|
||||||
|
}
|
||||||
|
|
||||||
private _addComments(comment: Comment) {
|
private _addComments(comment: Comment) {
|
||||||
if (this._comments) {
|
if (this._comments) {
|
||||||
this._comments.push(comment);
|
this._comments.push(comment);
|
||||||
|
@ -305,6 +305,24 @@ test('TypeIgnore5', () => {
|
|||||||
TestUtils.validateResults(analysisResults, 0, 1);
|
TestUtils.validateResults(analysisResults, 0, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('PyrightIgnore1', () => {
|
||||||
|
const configOptions = new ConfigOptions('.');
|
||||||
|
|
||||||
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore1.py'], configOptions);
|
||||||
|
TestUtils.validateResults(analysisResults, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PyrightIgnore2', () => {
|
||||||
|
const configOptions = new ConfigOptions('.');
|
||||||
|
|
||||||
|
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions);
|
||||||
|
TestUtils.validateResults(analysisResults, 2);
|
||||||
|
|
||||||
|
configOptions.diagnosticRuleSet.reportUnnecessaryTypeIgnoreComment = 'warning';
|
||||||
|
analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions);
|
||||||
|
TestUtils.validateResults(analysisResults, 2, 3);
|
||||||
|
});
|
||||||
|
|
||||||
test('DuplicateImports1', () => {
|
test('DuplicateImports1', () => {
|
||||||
const configOptions = new ConfigOptions('.');
|
const configOptions = new ConfigOptions('.');
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
# This sample tests the # pyright: ignore comment.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def foo(self, x: Optional[int]) -> str:
|
||||||
|
# This should suppress the error
|
||||||
|
x + "hi" # pyright: ignore - test
|
||||||
|
|
||||||
|
# This should not suppress the error because the rule doesn't match.
|
||||||
|
return 3 # pyright: ignore [foo]
|
@ -0,0 +1,24 @@
|
|||||||
|
# This sample tests the use of a # pyright: ignore comment in conjunction
|
||||||
|
# with the reportUnnecessaryTypeIgnoreComment mechanism.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def foo(self, x: Optional[int]) -> str:
|
||||||
|
# This should suppress the error
|
||||||
|
x + "hi" # pyright: ignore - test
|
||||||
|
|
||||||
|
# This is unnecessary
|
||||||
|
x + x # pyright: ignore
|
||||||
|
|
||||||
|
# This will not suppress the error
|
||||||
|
# These are both unnecessary
|
||||||
|
x + x # pyright: ignore [foo, bar]
|
||||||
|
|
||||||
|
# This will not suppress the error
|
||||||
|
x + x # pyright: ignore []
|
||||||
|
|
||||||
|
# One of these is unnecessary
|
||||||
|
x + "hi" # pyright: ignore [reportGeneralTypeIssues, foo]
|
||||||
|
|
||||||
|
return 3 # pyright: ignore [reportGeneralTypeIssues]
|
Loading…
Reference in New Issue
Block a user