feature-manifest 0.7.6

Document, validate, and render Cargo feature metadata.
Documentation
use std::fs;
use std::path::Path;

use serde_json::json;

use crate::cli::commands::check::PackageReport;
use crate::cli::output::github::issue_message;
use crate::cli::util::portable_relative_path;
use crate::{ManifestSourceMap, Severity, WorkspaceManifest, lint_docs};

pub fn render(
    workspace: &WorkspaceManifest,
    package_reports: &[PackageReport<'_>],
) -> serde_json::Value {
    let root_directory = workspace
        .root_manifest_path
        .parent()
        .unwrap_or_else(|| Path::new("."));

    let rules = lint_docs()
        .iter()
        .map(|lint| {
            json!({
                "id": lint.code,
                "name": lint.code,
                "shortDescription": { "text": lint.summary },
                "help": { "text": lint.guidance },
            })
        })
        .collect::<Vec<_>>();

    let results = package_reports
        .iter()
        .flat_map(|(package, report)| {
            let source = fs::read_to_string(&package.manifest_path).ok();
            report.issues.iter().map(move |issue| {
                let mut physical_location = json!({
                    "artifactLocation": {
                        "uri": portable_relative_path(root_directory, &package.manifest_path),
                    }
                });

                if let Some(span) = source
                    .as_deref()
                    .and_then(|source| ManifestSourceMap::new(source).span_for_issue(issue))
                {
                    physical_location["region"] = json!({
                        "startLine": span.line,
                        "startColumn": span.column,
                    });
                }

                json!({
                    "ruleId": issue.code,
                    "level": match issue.severity {
                        Severity::Warning => "warning",
                        Severity::Error => "error",
                    },
                    "message": {
                        "text": issue_message(package, issue),
                    },
                    "locations": [
                        {
                            "physicalLocation": physical_location,
                        }
                    ]
                })
            })
        })
        .collect::<Vec<_>>();

    json!({
        "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
        "version": "2.1.0",
        "runs": [
            {
                "tool": {
                    "driver": {
                        "name": "feature-manifest",
                        "informationUri": "https://github.com/funwithcthulhu/feature-manifest",
                        "rules": rules,
                    }
                },
                "results": results,
            }
        ]
    })
}