From 84583824771e12dc7801eb0958d4a9387a3ae79f Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Wed, 14 Dec 2022 09:20:15 -0800 Subject: [PATCH] Implemented a new `--level` command-line option that allows filtering of 'information' and 'warning' diagnostics. --- docs/command-line.md | 1 + packages/pyright-internal/src/pyright.ts | 77 +++++++++++++++++++++--- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/docs/command-line.md b/docs/command-line.md index 5ee828119..e9439d12e 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -11,6 +11,7 @@ Pyright can be run as either a VS Code extension or as a node-based command-line | -h, --help | Show help message | | --ignoreexternal | Ignore external imports for --verifytypes | | --lib | Use library code for types when stubs are missing | +| --level | Minimum diagnostic level (error or warning) | | --outputjson | Output results in JSON format | | -p, --project `` | Use the configuration file at this location | | --pythonplatform `` | Analyze for platform (Darwin, Linux, Windows) | diff --git a/packages/pyright-internal/src/pyright.ts b/packages/pyright-internal/src/pyright.ts index e3e4958ab..7475e0263 100644 --- a/packages/pyright-internal/src/pyright.ts +++ b/packages/pyright-internal/src/pyright.ts @@ -35,6 +35,8 @@ import { ChokidarFileWatcherProvider } from './common/chokidarFileWatcherProvide const toolName = 'pyright'; +type SeverityLevel = 'error' | 'warning' | 'information'; + enum ExitStatus { NoErrors = 0, ErrorsReported = 1, @@ -91,7 +93,7 @@ interface PyrightPublicSymbolReport { interface PyrightJsonDiagnostic { file: string; - severity: 'error' | 'warning' | 'information'; + severity: SeverityLevel; message: string; range?: Range | undefined; rule?: string | undefined; @@ -131,6 +133,7 @@ async function processArgs(): Promise { { name: 'help', alias: 'h', type: Boolean }, { name: 'ignoreexternal', type: Boolean }, { name: 'lib', type: Boolean }, + { name: 'level', type: String }, { name: 'outputjson', type: Boolean }, { name: 'project', alias: 'p', type: String }, { name: 'pythonplatform', type: String }, @@ -267,6 +270,17 @@ async function processArgs(): Promise { options.useLibraryCodeForTypes = true; } + let minSeverityLevel: SeverityLevel = 'information'; + if (args.level && typeof args.level === 'string') { + const levelValue = args.level.toLowerCase(); + if (levelValue === 'error' || levelValue === 'warning') { + minSeverityLevel = levelValue; + } else { + console.error(`'${args.level}' is not a valid value for --level; specify error or warning.`); + return ExitStatus.ParameterError; + } + } + options.checkOnlyOpenFiles = false; if (!!args.stats && !!args.verbose) { @@ -288,6 +302,7 @@ async function processArgs(): Promise { args['verifytypes'] || '', options, !!args.outputjson, + minSeverityLevel, args['ignoreexternal'] ); } else if (args['ignoreexternal'] !== undefined) { @@ -323,6 +338,7 @@ async function processArgs(): Promise { if (args.outputjson) { const report = reportDiagnosticsAsJson( results.diagnostics, + minSeverityLevel, results.filesInProgram, results.elapsedTime ); @@ -332,7 +348,7 @@ async function processArgs(): Promise { } } else { printVersion(); - const report = reportDiagnosticsAsText(results.diagnostics); + const report = reportDiagnosticsAsText(results.diagnostics, minSeverityLevel); errorCount += report.errorCount; if (treatWarningsAsErrors) { errorCount += report.warningCount; @@ -399,12 +415,13 @@ function verifyPackageTypes( packageName: string, options: PyrightCommandLineOptions, outputJson: boolean, + minSeverityLevel: SeverityLevel, ignoreUnknownTypesFromImports: boolean ): ExitStatus { try { const verifier = new PackageTypeVerifier(fileSystem, options, packageName, ignoreUnknownTypesFromImports); const report = verifier.verify(); - const jsonReport = buildTypeCompletenessReport(packageName, report); + const jsonReport = buildTypeCompletenessReport(packageName, report, minSeverityLevel); if (outputJson) { console.log(JSON.stringify(jsonReport, /* replacer */ undefined, 4)); @@ -434,7 +451,11 @@ function accumulateReportDiagnosticStats(diag: PyrightJsonDiagnostic, report: Py } } -function buildTypeCompletenessReport(packageName: string, completenessReport: PackageTypeReport): PyrightJsonResults { +function buildTypeCompletenessReport( + packageName: string, + completenessReport: PackageTypeReport, + minSeverityLevel: SeverityLevel +): PyrightJsonResults { const report: PyrightJsonResults = { version: getVersionString(), time: Date.now().toString(), @@ -451,7 +472,9 @@ function buildTypeCompletenessReport(packageName: string, completenessReport: Pa // Add the general diagnostics. completenessReport.generalDiagnostics.forEach((diag) => { const jsonDiag = convertDiagnosticToJson('', diag); - report.generalDiagnostics.push(jsonDiag); + if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) { + report.generalDiagnostics.push(jsonDiag); + } accumulateReportDiagnosticStats(jsonDiag, report); }); @@ -491,6 +514,16 @@ function buildTypeCompletenessReport(packageName: string, completenessReport: Pa // Add the symbols. completenessReport.symbols.forEach((symbol) => { + const diagnostics: PyrightJsonDiagnostic[] = []; + + // Convert and filter the diagnostics. + symbol.diagnostics.forEach((diag) => { + const jsonDiag = convertDiagnosticToJson(diag.filePath, diag.diagnostic); + if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) { + diagnostics.push(jsonDiag); + } + }); + const jsonSymbol: PyrightPublicSymbolReport = { category: PackageTypeVerifier.getSymbolCategoryString(symbol.category), name: symbol.fullName, @@ -498,7 +531,7 @@ function buildTypeCompletenessReport(packageName: string, completenessReport: Pa isExported: symbol.isExported, isTypeKnown: symbol.typeKnownStatus === TypeKnownStatus.Known, isTypeAmbiguous: symbol.typeKnownStatus === TypeKnownStatus.Ambiguous, - diagnostics: symbol.diagnostics.map((diag) => convertDiagnosticToJson(diag.filePath, diag.diagnostic)), + diagnostics, }; const alternateNames = completenessReport.alternateSymbolNames.get(symbol.fullName); @@ -655,6 +688,7 @@ function printUsage() { ' -h,--help Show this help message\n' + ' --ignoreexternal Ignore external imports for --verifytypes\n' + ' --lib Use library code to infer types when stubs are missing\n' + + ' --level Minimum diagnostic level (error or warning)\n' + ' --outputjson Output results in JSON format\n' + ' -p,--project Use the configuration file at this location\n' + ' --pythonplatform Analyze for a specific platform (Darwin, Linux, Windows)\n' + @@ -683,6 +717,7 @@ function printVersion() { function reportDiagnosticsAsJson( fileDiagnostics: FileDiagnostics[], + minSeverityLevel: SeverityLevel, filesInProgram: number, timeInSec: number ): DiagnosticResult { @@ -707,7 +742,10 @@ function reportDiagnosticsAsJson( diag.category === DiagnosticCategory.Information ) { const jsonDiag = convertDiagnosticToJson(fileDiag.filePath, diag); - report.generalDiagnostics.push(jsonDiag); + if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) { + report.generalDiagnostics.push(jsonDiag); + } + accumulateReportDiagnosticStats(jsonDiag, report); } }); @@ -723,6 +761,21 @@ function reportDiagnosticsAsJson( }; } +function isDiagnosticIncluded(diagSeverity: SeverityLevel, minSeverityLevel: SeverityLevel) { + // Errors are always included. + if (diagSeverity === 'error') { + return true; + } + + // Warnings are included only if the min severity level is below error. + if (diagSeverity === 'warning') { + return minSeverityLevel !== 'error'; + } + + // Informations are included only if the min severity level is 'information'. + return minSeverityLevel === 'information'; +} + function convertDiagnosticToJson(filePath: string, diag: Diagnostic): PyrightJsonDiagnostic { return { file: filePath, @@ -738,7 +791,10 @@ function convertDiagnosticToJson(filePath: string, diag: Diagnostic): PyrightJso }; } -function reportDiagnosticsAsText(fileDiagnostics: FileDiagnostics[]): DiagnosticResult { +function reportDiagnosticsAsText( + fileDiagnostics: FileDiagnostics[], + minSeverityLevel: SeverityLevel +): DiagnosticResult { let errorCount = 0; let warningCount = 0; let informationCount = 0; @@ -755,7 +811,10 @@ function reportDiagnosticsAsText(fileDiagnostics: FileDiagnostics[]): Diagnostic if (fileErrorsAndWarnings.length > 0) { console.log(`${fileDiagnostics.filePath}`); fileErrorsAndWarnings.forEach((diag) => { - logDiagnosticToConsole(convertDiagnosticToJson(fileDiagnostics.filePath, diag)); + const jsonDiag = convertDiagnosticToJson(fileDiagnostics.filePath, diag); + if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) { + logDiagnosticToConsole(jsonDiag); + } if (diag.category === DiagnosticCategory.Error) { errorCount++;