From c0b6f99ae7b3b9b4907dfd487ed9552fd9187ab4 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 21 Nov 2019 00:30:44 -0800 Subject: [PATCH] Added new 'outputjson' command-line option that formats output in JSON format. --- docs/command-line.md | 1 + server/src/pyright.ts | 108 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/docs/command-line.md b/docs/command-line.md index 42d934da6..80258ac65 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -7,6 +7,7 @@ Pyright can be run as either a VS Code extension or as a node-based command-line | --createstub IMPORT | Create type stub file(s) for import | | --dependencies | Emit import dependency information | | -h, --help | Show help message | +| --outputjson | Output results in JSON format | | -p, --project FILE OR DIRECTORY | Use the configuration file at this location | | --stats | Print detailed performance stats | | -t, --typeshed-path DIRECTORY | Use typeshed type stubs at this location (1) | diff --git a/server/src/pyright.ts b/server/src/pyright.ts index 52f78e503..c1cef4cd2 100644 --- a/server/src/pyright.ts +++ b/server/src/pyright.ts @@ -18,7 +18,8 @@ import * as process from 'process'; import { AnalyzerService } from './analyzer/service'; import { CommandLineOptions as PyrightCommandLineOptions } from './common/commandLineOptions'; -import { DiagnosticCategory } from './common/diagnostic'; +import { NullConsole } from './common/console'; +import { DiagnosticCategory, DiagnosticTextRange } from './common/diagnostic'; import { FileDiagnostics } from './common/diagnosticSink'; import { combinePaths, normalizePath } from './common/pathUtils'; @@ -31,6 +32,27 @@ enum ExitStatus { ConfigFileParseError = 3 } +interface PyrightJsonResults { + version: string; + time: string; + diagnostics: PyrightJsonDiagnostic[]; + summary: PyrightJsonSummary; +} + +interface PyrightJsonDiagnostic { + file: string; + severity: 'error' | 'warning'; + message: string; + range: DiagnosticTextRange; +} + +interface PyrightJsonSummary { + filesAnalyzed: number; + errorCount: number; + warningCount: number; + timeInSec: number; +} + interface DiagnosticResult { errorCount: number; warningCount: number; @@ -43,6 +65,7 @@ function processArgs() { { name: 'dependencies', type: Boolean }, { name: 'files', type: String, multiple: true, defaultOption: true }, { name: 'help', alias: 'h', type: Boolean }, + { name: 'outputjson', type: Boolean }, { name: 'project', alias: 'p', type: String }, { name: 'stats' }, { name: 'typeshed-path', alias: 't', type: String }, @@ -77,6 +100,16 @@ function processArgs() { return; } + if (args.outputjson) { + const incompatibleArgs = ['watch', 'stats', 'verbose', 'createstub', 'dependencies']; + for (const arg of incompatibleArgs) { + if (args[arg] !== undefined) { + console.error(`'outputjson' option cannot be used with '${ arg }' option`); + return; + } + } + } + const options = new PyrightCommandLineOptions(process.cwd(), false); // Assume any relative paths are relative to the working directory. @@ -115,7 +148,8 @@ function processArgs() { const watch = args.watch !== undefined; options.watch = watch; - const service = new AnalyzerService(''); + const service = new AnalyzerService('', args.outputjson ? + new NullConsole() : undefined); service.setCompletionCallback(results => { if (results.fatalErrorOccurred) { @@ -128,8 +162,14 @@ function processArgs() { let errorCount = 0; if (results.diagnostics.length > 0 && !args.createstub) { - const report = reportDiagnostics(results.diagnostics); - errorCount += report.errorCount; + if (args.outputjson) { + const report = reportDiagnosticsAsJson(results.diagnostics, + results.filesInProgram, results.elapsedTime); + errorCount += report.errorCount; + } else { + const report = reportDiagnosticsAsText(results.diagnostics); + errorCount += report.errorCount; + } } if (args.createstub && results.filesRequiringAnalysis === 0) { @@ -190,6 +230,7 @@ function printUsage() { ' --createstub IMPORT Create type stub file(s) for import\n' + ' --dependencies Emit import dependency information\n' + ' -h,--help Show this help message\n' + + ' --outputjson Output results in JSON format\n' + ' -p,--project FILE OR DIRECTORY Use the configuration file at this location\n' + ' --stats Print detailed performance stats\n' + ' -t,--typeshed-path DIRECTORY Use typeshed type stubs at this location\n' + @@ -200,12 +241,65 @@ function printUsage() { ); } -function printVersion() { +function getVersionString() { const version = require('package.json').version; - console.log(`${ toolName } ${ version.toString() }`); + return version.toString(); } -function reportDiagnostics(fileDiagnostics: FileDiagnostics[]): DiagnosticResult { +function printVersion() { + console.log(`${ toolName } ${ getVersionString() }`); +} + +function reportDiagnosticsAsJson(fileDiagnostics: FileDiagnostics[], filesInProgram: number, + timeInSec: number): DiagnosticResult { + + const report: PyrightJsonResults = { + version: getVersionString(), + time: Date.now().toString(), + diagnostics: [], + summary: { + filesAnalyzed: filesInProgram, + errorCount: 0, + warningCount: 0, + timeInSec + } + }; + + let errorCount = 0; + let warningCount = 0; + + fileDiagnostics.forEach(fileDiag => { + fileDiag.diagnostics.forEach(diag => { + if (diag.category === DiagnosticCategory.Error || diag.category === DiagnosticCategory.Warning) { + report.diagnostics.push({ + file: fileDiag.filePath, + severity: diag.category === DiagnosticCategory.Error ? 'error' : 'warning', + message: diag.message, + range: diag.range + }); + + if (diag.category === DiagnosticCategory.Error) { + errorCount++; + } else { + warningCount++; + } + } + }); + }); + + report.summary.errorCount = errorCount; + report.summary.warningCount = warningCount; + + console.log(JSON.stringify(report, undefined, 4)); + + return { + errorCount, + warningCount, + diagnosticCount: errorCount + warningCount + }; +} + +function reportDiagnosticsAsText(fileDiagnostics: FileDiagnostics[]): DiagnosticResult { let errorCount = 0; let warningCount = 0;