cargo-gears-lints 0.0.1

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

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

dylint_linting::declare_early_lint! {
    /// DE0201: DTOs Only in API Rest Folder
    ///
    /// Types with DTO suffixes must be defined only in `*/api/rest/*.rs` files.
    #[doc = include_str!("../../docs/de02_api_layer/de0201_dtos_only_in_api_rest/README.md")]
    pub DE0201_DTOS_ONLY_IN_API_REST,
    Deny,
    "DTO types should only be defined in */api/rest/* files (DE0201)"
}

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

        // Check if item name ends with "Dto"
        let (item_name, span) = match &item.kind {
            ItemKind::Struct(ident, ..) => {
                let span = item.span.with_hi(ident.span.hi());
                (ident.name.as_str(), span)
            }
            ItemKind::Enum(ident, ..) => {
                let span = item.span.with_hi(ident.span.hi());
                (ident.name.as_str(), span)
            }
            _ => return,
        };

        if !item_name.to_lowercase().ends_with("dto") {
            return;
        }

        // Check if the file is in api/rest folder (supports simulated_dir for tests)
        if !crate::lint_utils::is_in_api_rest_folder(cx.sess().source_map(), item.span) {
            cx.span_lint(DE0201_DTOS_ONLY_IN_API_REST, span, |diag| {
                diag.primary_message(format!(
                    "DTO type `{}` is defined outside of api/rest folder (DE0201)",
                    item_name
                ));
                diag.help("move DTO types to src/api/rest/dto.rs");
            });
        }
    }
}