UNPKG

npm-groovy-lint

Version:

Lint, format and auto-fix your Groovy / Jenkinsfile / Gradle files

295 lines (276 loc) 12.3 kB
// Output management import ansiColors from "ansi-colors"; import fs from "fs-extra"; import { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder } from "node-sarif-builder"; import * as path from "path"; import { isErrorInLogLevelScope, getNpmGroovyLintVersion } from "./utils.js"; // Compute statistics for output function computeStats(lintResult) { const counterResultsSummary = { totalFoundNumber: 0, totalFixedNumber: 0, totalRemainingNumber: 0, totalFoundErrorNumber: 0, totalFoundWarningNumber: 0, totalFoundInfoNumber: 0, totalFixedErrorNumber: 0, totalFixedWarningNumber: 0, totalFixedInfoNumber: 0, totalRemainingErrorNumber: 0, totalRemainingWarningNumber: 0, totalRemainingInfoNumber: 0, detectedRules: {}, fixedRules: {}, }; // No files = no result (mostly happens when lint has been cancelled before being completed) if (lintResult.files == null) { return lintResult; } for (const fileName of Object.keys(lintResult.files)) { const fileResults = lintResult.files[fileName]; const fileErrors = fileResults.errors || []; const fileErrorStats = appendErrorTypeStats("error", fileErrors, counterResultsSummary); const fileWarningStats = appendErrorTypeStats("warning", fileErrors, counterResultsSummary); const fileInfoStats = appendErrorTypeStats("info", fileErrors, counterResultsSummary); // Total ignoring severity counterResultsSummary.totalFoundNumber = counterResultsSummary.totalFoundNumber + fileErrorStats.found + fileWarningStats.found + fileInfoStats.found; counterResultsSummary.totalFixedNumber = counterResultsSummary.totalFixedNumber + fileErrorStats.fixed + fileWarningStats.fixed + fileInfoStats.fixed; counterResultsSummary.totalRemainingNumber = counterResultsSummary.totalRemainingNumber + fileErrorStats.remaining + fileWarningStats.remaining + fileInfoStats.remaining; } // Set summary lintResult.summary = Object.assign(lintResult.summary || {}, counterResultsSummary); return lintResult; } // Collect stats about errors in a linted / formatted / fixed file function appendErrorTypeStats(severity, fileErrors, counterResultsSummary) { // Found const fileFoundSeverityNumber = fileErrors.filter((err) => { return err.severity === severity; }).length; // Fixed const fileFixedSeverityNumber = fileErrors.filter((err) => { return err.severity === severity && err.fixed && err.fixed === true; }).length; // Remaining const fileRemainingSeverityNumber = fileFoundSeverityNumber - fileFixedSeverityNumber; // Add counters for each problem type for (const err of fileErrors) { if (err.severity === severity) { counterResultsSummary.detectedRules[err.rule] = counterResultsSummary.detectedRules[err.rule] ? counterResultsSummary.detectedRules[err.rule] + 1 : 1; if (err.fixed && err.fixed === true) { counterResultsSummary.fixedRules[err.rule] = counterResultsSummary.fixedRules[err.rule] ? counterResultsSummary.fixedRules[err.rule] + 1 : 1; } } } // Update summary & build result const severityCapital = `${severity[0].toUpperCase()}${severity.slice(1)}`; const totalFoundKey = `totalFound${severityCapital}Number`; const totalFixedKey = `totalFixed${severityCapital}Number`; const totalRemainingKey = `totalRemaining${severityCapital}Number`; counterResultsSummary[totalFoundKey] = counterResultsSummary[totalFoundKey] + fileFoundSeverityNumber; counterResultsSummary[totalFixedKey] = counterResultsSummary[totalFixedKey] + fileFixedSeverityNumber; counterResultsSummary[totalRemainingKey] = counterResultsSummary[totalRemainingKey] + fileRemainingSeverityNumber; return { found: fileFoundSeverityNumber, fixed: fileFixedSeverityNumber, remaining: fileRemainingSeverityNumber, }; } // Reformat output if requested in command line async function processOutput(outputType, output, lintResult, options, fixer = null) { let outputString = ""; // Display as console log if (outputType === "txt") { // Disable colors if output results in text file or no output result if (output.includes(".txt") || output === "none") { // Disable ansi colors if output in txt file ansiColors.enabled = false; } // Errors for (const fileNm of Object.keys(lintResult.files)) { const fileErrors = lintResult.files[fileNm].errors; let fileOutputString = ansiColors.underline(fileNm) + "\n"; let showFileInOutput = false; for (const err of fileErrors) { if (!isErrorInLogLevelScope(err.severity, options.loglevel)) { continue; } showFileInOutput = true; let color = "grey"; switch (err.severity) { case "error": color = "red"; break; case "warning": color = "yellow"; break; case "info": color = "grey"; break; } // Display fixed errors only if --verbose is called if (err.fixed === true) { if (options.verbose === true) { color = "green"; err.severity = "fixed"; } else { continue; } } // Build error output line fileOutputString += " " + err.line.toString().padEnd(4, " ") + " " + ansiColors[color](err.severity.padEnd(7, " ")) + " " + err.msg + " " + err.rule.padEnd(24, " ") + "\n"; } fileOutputString += "\n"; if (showFileInOutput || options.verbose) { outputString += fileOutputString; } } outputString += "\nnpm-groovy-lint results in " + ansiColors.bold(lintResult.summary.totalFilesLinted) + " linted files:"; // Summary table const errorTableLine = { Severity: "Error", "Total found": lintResult.summary.totalFoundErrorNumber, "Total fixed": lintResult.summary.totalFixedErrorNumber, "Total remaining": lintResult.summary.totalRemainingErrorNumber, }; const warningTableLine = { Severity: "Warning", "Total found": lintResult.summary.totalFoundWarningNumber, "Total fixed": lintResult.summary.totalFixedWarningNumber, "Total remaining": lintResult.summary.totalRemainingWarningNumber, }; const infoTableLine = { Severity: "Info", "Total found": lintResult.summary.totalFoundInfoNumber, "Total fixed": lintResult.summary.totalFixedInfoNumber, "Total remaining": lintResult.summary.totalRemainingInfoNumber, }; const summaryTable = []; if (isErrorInLogLevelScope("error", options.loglevel)) { summaryTable.push(errorTableLine); } if (isErrorInLogLevelScope("warning", options.loglevel)) { summaryTable.push(warningTableLine); } if (isErrorInLogLevelScope("warning", options.loglevel)) { summaryTable.push(infoTableLine); } // Output text log in file if (output.endsWith(".txt")) { const fullFileContent = outputString; fs.writeFileSync(output, fullFileContent); console.table(summaryTable, fixer ? ["Severity", "Total found", "Total fixed", "Total remaining"] : ["Severity", "Total found"]); const absolutePath = path.resolve(".", output); console.info("GroovyLint: Logged results in file " + absolutePath); } else { // Output text log in console console.log(outputString); console.table(summaryTable, fixer ? ["Severity", "Total found", "Total fixed", "Total remaining"] : ["Severity", "Total found"]); } } // Display as json else if (outputType === "json") { // Output log if (output.endsWith(".json")) { const fullFileContent = JSON.stringify(lintResult, null, 2); fs.writeFileSync(output, fullFileContent); const absolutePath = path.resolve(".", output); console.info("GroovyLint: Logged results in file " + absolutePath); } else { outputString = JSON.stringify(lintResult); console.log(outputString); } } // SARIF results else if (outputType === "sarif") { const sarifJsonString = buildSarifResult(lintResult); // SARIF file if (output.endsWith(".sarif")) { fs.writeFileSync(output, sarifJsonString); const absolutePath = path.resolve(".", output); outputString = "GroovyLint: Logged SARIF results in file " + absolutePath; console.info(outputString); } else { // SARIF in stdout outputString = sarifJsonString; console.log(sarifJsonString); } } // stdout result for format / fix with source sent as stdin else if (outputType === "stdout") { console.log(lintResult.files[0].updatedSource); } return outputString; } function buildSarifResult(lintResult) { // SARIF builder const sarifBuilder = new SarifBuilder(); // SARIF Run builder const sarifRunBuilder = new SarifRunBuilder().initSimple({ toolDriverName: "npm-groovy-lint", toolDriverVersion: getNpmGroovyLintVersion(), url: "https://nvuillam.github.io/npm-groovy-lint/", }); // SARIF rules for (const ruleId of Object.keys(lintResult.rules || {})) { const rule = lintResult.rules[ruleId]; const sarifRuleBuilder = new SarifRuleBuilder().initSimple({ ruleId: ruleId, shortDescriptionText: rule.description, helpUri: rule.docUrl, }); sarifRunBuilder.addRule(sarifRuleBuilder); } // Add SARIF results (individual errors) for (const fileNm of Object.keys(lintResult.files)) { const fileErrors = lintResult.files[fileNm].errors; for (const err of fileErrors) { const sarifResultBuilder = new SarifResultBuilder(); const sarifResultInit = { level: err.severity === "info" ? "note" : err.severity, // Other values can be "warning" or "error" messageText: err.msg, ruleId: err.rule, fileUri: process.env.SARIF_URI_ABSOLUTE ? "file:///" + fileNm.replace(/\\/g, "/") : path.relative(process.cwd(), fileNm), }; if (err && err.range && err.range.start && (err.range.start.line === 0 || err.range.start.line > 0)) { sarifResultInit.startLine = fixLine(err.range.start.line); sarifResultInit.startColumn = fixCol(err.range.start.character); sarifResultInit.endLine = fixLine(err.range.end.line); sarifResultInit.endColumn = fixCol(err.range.end.character); } sarifResultBuilder.initSimple(sarifResultInit); sarifRunBuilder.addResult(sarifResultBuilder); } } sarifBuilder.addRun(sarifRunBuilder); return sarifBuilder.buildSarifJsonString({ indent: false }); } function fixLine(val) { if (val === null) { return undefined; } return val === 0 ? 1 : val; } function fixCol(val) { if (val === null) { return undefined; } return val === 0 ? 1 : val + 1; } export { computeStats, processOutput };