merman-analysis 0.8.0-alpha.3

Diagnostics-first analysis contracts and source mapping for Merman.
Documentation
use crate::rules::{
    RESOURCE_LIMIT_RULE_ID, internal_rule_registry_gap_diagnostic, rule_descriptor,
};
use crate::{AnalysisDiagnostic, AnalysisResult, AnalysisStatus, SourceDescriptor, SourceMap};

pub(crate) fn source_limit_result(
    source: &str,
    descriptor: SourceDescriptor,
    max_source_bytes: Option<usize>,
) -> Option<AnalysisResult> {
    let diagnostics = source_limit_diagnostics(source, max_source_bytes)?;
    Some(AnalysisResult::new(
        descriptor,
        SourceMap::new(""),
        diagnostics,
        Vec::new(),
    ))
}

pub(crate) fn source_limit_diagnostics(
    source: &str,
    max_source_bytes: Option<usize>,
) -> Option<Vec<AnalysisDiagnostic>> {
    let limit = max_source_bytes?;
    if source.len() <= limit {
        return None;
    }

    Some(vec![source_limit_diagnostic(source, limit)])
}

fn source_limit_diagnostic(source: &str, limit: usize) -> AnalysisDiagnostic {
    let span = crate::source_map::whole_text_span_without_source_copy(source);
    source_limit_diagnostic_for_len_and_span(source.len(), limit, span)
}

pub fn source_limit_diagnostic_for_len(source_len: usize, limit: usize) -> AnalysisDiagnostic {
    let span = crate::DiagnosticSpan::new(
        0,
        0,
        1,
        1,
        1,
        1,
        crate::Utf16Position {
            line: 0,
            character: 0,
        },
        crate::Utf16Position {
            line: 0,
            character: 0,
        },
    );
    source_limit_diagnostic_for_len_and_span(source_len, limit, span)
}

pub fn source_discarded_after_limit_change_diagnostic(
    source_len: usize,
    previous_limit: usize,
) -> AnalysisDiagnostic {
    let span = crate::DiagnosticSpan::new(
        0,
        0,
        1,
        1,
        1,
        1,
        crate::Utf16Position {
            line: 0,
            character: 0,
        },
        crate::Utf16Position {
            line: 0,
            character: 0,
        },
    );
    let message = format!(
        "source is {source_len} bytes and was discarded after exceeding previous max_source_bytes {previous_limit}; reopen the document or send a full document replacement to analyze it with the current limit"
    );
    let Some(descriptor) = rule_descriptor(RESOURCE_LIMIT_RULE_ID) else {
        return internal_rule_registry_gap_diagnostic(
            format!(
                "unknown analysis rule id `{RESOURCE_LIMIT_RULE_ID}` while emitting diagnostic: {message}"
            ),
            Some(span),
        );
    };

    AnalysisDiagnostic::new(
        descriptor.id,
        descriptor.default_severity,
        descriptor.category,
        message,
    )
    .with_code(
        AnalysisStatus::ResourceLimitExceeded.code(),
        AnalysisStatus::ResourceLimitExceeded.code_name(),
    )
    .with_span(span)
}

fn source_limit_diagnostic_for_len_and_span(
    source_len: usize,
    limit: usize,
    span: crate::DiagnosticSpan,
) -> AnalysisDiagnostic {
    let message = format!("source is {source_len} bytes, exceeding max_source_bytes {limit}");
    let Some(descriptor) = rule_descriptor(RESOURCE_LIMIT_RULE_ID) else {
        return internal_rule_registry_gap_diagnostic(
            format!(
                "unknown analysis rule id `{RESOURCE_LIMIT_RULE_ID}` while emitting diagnostic: {message}"
            ),
            Some(span),
        );
    };

    AnalysisDiagnostic::new(
        descriptor.id,
        descriptor.default_severity,
        descriptor.category,
        message,
    )
    .with_code(
        AnalysisStatus::ResourceLimitExceeded.code(),
        AnalysisStatus::ResourceLimitExceeded.code_name(),
    )
    .with_span(span)
}