cargo-gears-lints 0.0.1

Dylint lint collection for cargo-gears architectural rules
extern crate rustc_ast;

use rustc_ast::{Item, ItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};

use crate::lint_utils::is_in_domain_path;

dylint_linting::declare_pre_expansion_lint! {
    /// ### What it does
    ///
    /// Checks that structs and enums in domain modules do not derive Serialize or Deserialize.
    ///
    /// ### Why is this bad?
    ///
    /// Domain models should remain independent of serialization concerns.
    /// Use DTOs (Data Transfer Objects) in the API layer for serialization instead.
    ///
    /// ### Example
    ///
    /// ```rust
    /// // Bad - domain model derives serde traits
    /// mod domain {
    ///     use serde::Serialize;
    ///     #[derive(Serialize)]
    ///     pub struct User { pub id: String }
    /// }
    /// ```
    ///
    /// Use instead:
    ///
    /// ```rust
    /// // Good - domain model without serde
    /// mod domain {
    ///     pub struct User { pub id: String }
    /// }
    ///
    /// // Separate DTO in API layer
    /// mod api {
    ///     use serde::Serialize;
    ///     #[derive(Serialize)]
    ///     pub struct UserDto { pub id: String }
    /// }
    /// ```
    #[doc = include_str!("../../docs/de01_domain_layer/de0101_no_serde_in_domain/README.md")]
    pub DE0101_NO_SERDE_IN_CONTRACT,
    Deny,
    "domain models should not have serde derives (DE0101)"
}

impl EarlyLintPass for De0101NoSerdeInContract {
    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
        // Only check structs and enums
        if !matches!(item.kind, ItemKind::Struct(..) | ItemKind::Enum(..)) {
            return;
        }

        if !is_in_domain_path(cx.sess().source_map(), item.span) {
            return;
        }

        // Check for serde derives
        crate::lint_utils::check_derive_attrs(item, |meta_item, attr| {
            let segments = crate::lint_utils::get_derive_path_segments(meta_item);

            // Check if this is a serde Serialize or Deserialize
            // Handles: Serialize, serde::Serialize, ::serde::Serialize
            let is_serialize = crate::lint_utils::is_serde_trait(&segments, "Serialize");
            let is_deserialize = crate::lint_utils::is_serde_trait(&segments, "Deserialize");

            if is_serialize {
                cx.span_lint(DE0101_NO_SERDE_IN_CONTRACT, attr.span, |diag| {
                    diag.primary_message("domain type should not derive `Serialize` (DE0101)");
                    diag.help("remove serde derives from domain models; use DTOs in the API layer");
                });
            } else if is_deserialize {
                cx.span_lint(DE0101_NO_SERDE_IN_CONTRACT, attr.span, |diag| {
                    diag.primary_message("domain type should not derive `Deserialize` (DE0101)");
                    diag.help("remove serde derives from domain models; use DTOs in the API layer");
                });
            }
        });
    }
}