diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 6bd8cf9a10e5..155659eff1cc 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -3881,11 +3881,15 @@ export class Checker extends ParseTreeWalker { const action = rule === DiagnosticRule.reportUnusedImport ? { action: Commands.unusedImport } : undefined; if (nameNode) { - this._fileInfo.diagnosticSink.addUnusedCodeWithTextRange( - LocMessage.unaccessedSymbol().format({ name: nameNode.d.value }), - nameNode, - action - ); + const isAllowedUnusedVariable = + rule === DiagnosticRule.reportUnusedVariable && nameNode.d.value.startsWith('_'); + if (!isAllowedUnusedVariable) { + this._fileInfo.diagnosticSink.addUnusedCodeWithTextRange( + LocMessage.unaccessedSymbol().format({ name: nameNode.d.value }), + nameNode, + action + ); + } if (rule !== undefined && message && diagnosticLevel !== 'none') { this._evaluator.addDiagnostic(rule, message, nameNode); diff --git a/packages/pyright-internal/src/tests/samples/unusedVariable2.py b/packages/pyright-internal/src/tests/samples/unusedVariable2.py new file mode 100644 index 000000000000..485b1ddda172 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/unusedVariable2.py @@ -0,0 +1,8 @@ +# This sample tests that underscore-prefixed variables are not reported as unused. + + +def func(_arg: int, arg: int): + _local = 1 + + # This should generate both an error and an unused code diagnostic. + regular = 1 diff --git a/packages/pyright-internal/src/tests/typeEvaluator8.test.ts b/packages/pyright-internal/src/tests/typeEvaluator8.test.ts index 2af5098a6d43..1c5f29b46297 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator8.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator8.test.ts @@ -798,6 +798,31 @@ test('UnusedVariable1', () => { TestUtils.validateResults(analysisResults2, 3); }); +test('UnusedVariable2', () => { + const configOptions = new ConfigOptions(Uri.empty()); + const sampleLines = TestUtils.readSampleFile('unusedVariable2.py').split(/\r?\n/); + + const validateUnusedNames = (analysisResults: TestUtils.FileAnalysisResult[]) => { + const unusedNames = analysisResults[0].unusedCodes.map((diagnostic) => { + assert.strictEqual(diagnostic.range.start.line, diagnostic.range.end.line); + const line = sampleLines[diagnostic.range.start.line]; + return line.slice(diagnostic.range.start.character, diagnostic.range.end.character); + }); + + assert.deepStrictEqual(unusedNames, ['arg', 'regular']); + }; + + configOptions.diagnosticRuleSet.reportUnusedVariable = 'none'; + const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['unusedVariable2.py'], configOptions); + TestUtils.validateResults(analysisResults1, 0, 0, 0, 2); + validateUnusedNames(analysisResults1); + + configOptions.diagnosticRuleSet.reportUnusedVariable = 'error'; + const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['unusedVariable2.py'], configOptions); + TestUtils.validateResults(analysisResults2, 1, 0, 0, 2); + validateUnusedNames(analysisResults2); +}); + test('Descriptor1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['descriptor1.py']);