slicec 0.4.0

The Slice parser and other core components for Slice compilers.
Documentation
// Copyright (c) ZeroC, Inc.

use crate::diagnostics::{Diagnostic, Diagnostics, Lint};
use crate::grammar::*;

pub fn validate_operation(operation: &Operation, diagnostics: &mut Diagnostics) {
    if let Some(comment) = operation.comment() {
        validate_param_tags(comment, operation, diagnostics);
        validate_returns_tags(comment, operation, diagnostics);
    }
}

fn validate_param_tags(comment: &DocComment, operation: &Operation, diagnostics: &mut Diagnostics) {
    let parameters: Vec<_> = operation.parameters().iter().map(|p| p.identifier()).collect();

    for param_tag in &comment.params {
        let tag_identifier = param_tag.identifier.value.as_str();
        if !parameters.contains(&tag_identifier) {
            Diagnostic::from_lint(Lint::IncorrectDocComment {
                message: format!(
                    "comment has a 'param' tag for '{tag_identifier}', but operation '{}' has no parameter with that name",
                    operation.identifier(),
                ),
            })
            .set_span(param_tag.span())
            .set_scope(operation.parser_scoped_identifier())
            .push_into(diagnostics);
        }
    }
}

fn validate_returns_tags(comment: &DocComment, operation: &Operation, diagnostics: &mut Diagnostics) {
    let returns_tags = &comment.returns;
    match operation.return_members().as_slice() {
        // If the operation doesn't return anything, but its doc comment has 'returns' tags, report an error.
        [] => validate_returns_tags_for_operation_with_no_return_type(returns_tags, operation, diagnostics),

        // If the operation returns a single type, ensure that its 'returns' tag doesn't have an identifier.
        [_] => validate_returns_tags_for_operation_with_single_return(returns_tags, operation, diagnostics),

        // If the operation returns a tuple, ensure its returns tags use identifiers matching the tuple's.
        tuple => validate_returns_tags_for_operation_with_return_tuple(returns_tags, operation, tuple, diagnostics),
    }
}

fn validate_returns_tags_for_operation_with_no_return_type(
    returns_tags: &[ReturnsTag],
    operation: &Operation,
    diagnostics: &mut Diagnostics,
) {
    for returns_tag in returns_tags {
        Diagnostic::from_lint(Lint::IncorrectDocComment {
            message: format!(
                "comment has a 'returns' tag, but operation '{}' does not return anything",
                operation.identifier(),
            ),
        })
        .set_span(&(returns_tag.span() + returns_tag.message.span()))
        .set_scope(operation.parser_scoped_identifier())
        .push_into(diagnostics);
    }
}

fn validate_returns_tags_for_operation_with_single_return(
    returns_tags: &[ReturnsTag],
    operation: &Operation,
    diagnostics: &mut Diagnostics,
) {
    for returns_tag in returns_tags {
        if let Some(tag_identifier) = &returns_tag.identifier {
            Diagnostic::from_lint(Lint::IncorrectDocComment {
                message: format!(
                    "comment has a 'returns' tag for '{}', but operation '{}' doesn't return anything with that name",
                    tag_identifier.value,
                    operation.identifier(),
                ),
            })
            .set_span(returns_tag.span())
            .set_scope(operation.parser_scoped_identifier())
            .add_note(
                format!("operation '{}' returns a single unnamed type", operation.identifier()),
                Some(operation.span()),
            )
            .add_note("try removing the identifier from your comment: \"@returns: ...\"", None)
            .push_into(diagnostics);
        }
    }
}

fn validate_returns_tags_for_operation_with_return_tuple(
    returns_tags: &[ReturnsTag],
    operation: &Operation,
    return_tuple: &[&Parameter],
    diagnostics: &mut Diagnostics,
) {
    let return_members: Vec<_> = return_tuple.iter().map(|p| p.identifier()).collect();

    for returns_tag in returns_tags {
        if let Some(tag_identifier) = &returns_tag.identifier {
            let tag_identifier = tag_identifier.value.as_str();
            if !return_members.contains(&tag_identifier) {
                Diagnostic::from_lint(Lint::IncorrectDocComment {
                    message: format!(
                        "comment has a 'returns' tag for '{tag_identifier}', but operation '{}' doesn't return anything with that name",
                        operation.identifier(),
                    ),
                })
                .set_span(returns_tag.span())
                .set_scope(operation.parser_scoped_identifier())
                .push_into(diagnostics);
            }
        }
    }
}