@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
117 lines • 5.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinterOutputFormat = void 0;
exports.formatLints = formatLints;
/**
* Machine-readable renderings of linting results, for the tools that consume them (a CI, a code scanner, an editor).
* @module
*/
const linter_rules_1 = require("./linter-rules");
const linter_format_1 = require("./linter-format");
const files_1 = require("../util/files");
const doc_files_1 = require("../documentation/doc-util/doc-files");
/** The formats linting results are reported in. */
var LinterOutputFormat;
(function (LinterOutputFormat) {
/** flowR's own summary, made to be read by a human. The default, and the one {@link formatLints} leaves to flowR. */
LinterOutputFormat["Text"] = "text";
/** [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html), e.g. to upload to GitHub code scanning */
LinterOutputFormat["Sarif"] = "sarif";
/** [GitHub workflow commands](https://docs.github.com/actions/reference/workflow-commands-for-github-actions), one annotation per finding */
LinterOutputFormat["Github"] = "github";
})(LinterOutputFormat || (exports.LinterOutputFormat = LinterOutputFormat = {}));
/** flattened, as neither format groups by rule */
function findings(results) {
return Object.entries(results).flatMap(([name, perRuleResults]) => {
const rule = name;
const perRule = perRuleResults;
/* staying silent about a rule that threw would claim it passed */
if (linter_format_1.LintingResults.isError(perRule)) {
return [{ rule, message: `the linting rule failed: ${linter_format_1.LintingResults.stringifyError(perRule)}`, level: 'error', loc: undefined }];
}
return perRule.results.map(result => ({
rule,
message: linter_rules_1.LintingRules[rule].prettyPrint[linter_format_1.LintingPrettyPrintContext.Query](result, perRule['.meta']),
level: (result.certainty === linter_format_1.LintingResultCertainty.Certain ? 'warning' : 'note'),
loc: result.loc
}));
});
}
/** Neither a github annotation nor its sarif ingestion attaches to an absolute path, so report it from the workspace. */
function reportedPath(file) {
return (0, files_1.relativeTo)(process.env.GITHUB_WORKSPACE ?? process.cwd(), file);
}
/** a finding flowR cannot locate carries no location, rather than a broken one */
function sarifLocation(loc) {
if (loc === undefined || loc[4] === undefined) {
return [];
}
return [{
physicalLocation: {
artifactLocation: { uri: reportedPath(loc[4]) },
region: { startLine: loc[0], startColumn: loc[1], endLine: loc[2], endColumn: loc[3] }
}
}];
}
/**
* Renders the linting results as SARIF 2.1.0, the format code scanners (e.g. GitHub's) ingest, on a single line.
* Only the rules that produced a finding are described, as SARIF requires every reported rule to be declared.
*/
function lintsToSarif(results, flowrVersion) {
const flat = findings(results);
const reported = [...new Set(flat.map(f => f.rule))];
return JSON.stringify({
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
version: '2.1.0',
runs: [{
tool: {
driver: {
name: 'flowR',
informationUri: doc_files_1.FlowrGithubRef,
version: flowrVersion,
rules: reported.map(name => ({
id: name,
name: linter_rules_1.LintingRules[name].info.name,
shortDescription: { text: linter_rules_1.LintingRules[name].info.description },
properties: { tags: linter_rules_1.LintingRules[name].info.tags }
}))
}
},
results: flat.map(f => ({
ruleId: f.rule,
level: f.level,
message: { text: f.message },
locations: sarifLocation(f.loc)
}))
}]
});
}
/** GitHub has no 'note' */
const githubCommand = { error: 'error', warning: 'warning', note: 'notice' };
/** `%`, `\r` and `\n` would end the workflow command */
function escapeGithubData(text) {
return text.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A');
}
/** Renders the linting results as GitHub workflow commands, which a workflow run turns into annotations. */
function lintsToGithub(results) {
return findings(results).map(f => {
const loc = f.loc !== undefined && f.loc[4] !== undefined ? f.loc : undefined;
const where = loc === undefined ? '' : ` file=${escapeGithubData(reportedPath(loc[4]))},line=${loc[0]},col=${loc[1]},endLine=${loc[2]},endColumn=${loc[3]},`;
return `::${githubCommand[f.level]}${where}${loc === undefined ? ' ' : ''}title=${escapeGithubData(f.rule)}::${escapeGithubData(f.message)}`;
}).join('\n');
}
/**
* Renders the linting results in the given {@link LinterOutputFormat|format}, `undefined` for
* {@link LinterOutputFormat.Text|Text}: that one is flowR's own summary, which only flowR itself renders.
*/
function formatLints(results, format, flowrVersion) {
switch (format) {
case LinterOutputFormat.Sarif:
return lintsToSarif(results, flowrVersion);
case LinterOutputFormat.Github:
return lintsToGithub(results);
case LinterOutputFormat.Text:
return undefined;
}
}
//# sourceMappingURL=linter-output.js.map