rrubocop 0.8.15

10x faster RuboCop drop-in. Same output format and configs, without maintaining RuboCop gems.
//! Layout/EndAlignment.

use tree_sitter::Node;

use crate::cop::layout::end_align;
use crate::cop::{Cop, CopConfig};
use crate::correction::Correction;
use crate::diagnostic::Diagnostic;
use crate::parse::source::SourceFile;

pub struct EndAlignment;

fn base_name(kind: &str) -> &'static str {
    match kind {
        "class" => "class",
        "module" => "module",
        "if" => "if",
        "unless" => "unless",
        "while" => "while",
        "until" => "until",
        "case" => "case",
        _ => "def",
    }
}

impl Cop for EndAlignment {
    fn name(&self) -> &'static str {
        "Layout/EndAlignment"
    }
    fn supports_autocorrect(&self) -> bool {
        true
    }
    fn interested_node_kinds(&self) -> &'static [&'static str] {
        // `begin`/`end` is Layout/BeginEndAlignment; `do`/`end` is BlockAlignment.
        // RuboCop EndAlignment has no `on_kwbegin`.
        &[
            "class",
            "module",
            "if",
            "unless",
            "while",
            "until",
            "case",
        ]
    }

    fn check_node(
        &self,
        source: &SourceFile,
        node: Node<'_>,
        config: &CopConfig,
        diagnostics: &mut Vec<Diagnostic>,
        mut corrections: Option<&mut Vec<Correction>>,
    ) {
        end_align::check_end(
            self,
            source,
            node,
            base_name(node.kind()),
            config.get_str("EnforcedStyleAlignWith", "keyword"),
            diagnostics,
            &mut corrections,
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testutil::run_cop_full_with_config;
    use std::collections::HashMap;

    crate::cop_fixture_tests!(EndAlignment, "cops/layout/end_alignment");

    fn variable_config() -> CopConfig {
        CopConfig {
            options: HashMap::from([(
                "EnforcedStyleAlignWith".into(),
                serde_yml::Value::String("variable".into()),
            )]),
            ..CopConfig::default()
        }
    }

    #[test]
    fn variable_style_operator_assignment_if_no_offense() {
        let diags = run_cop_full_with_config(
            &EndAlignment,
            b"      trades.each do |t|\n        total_from += if cr.from_currency_id == t.currency_id\n          t.volume\n        else\n          t.funds\n        end\n      end\n",
            variable_config(),
        );
        assert!(
            diags.is_empty(),
            "variable style should align end with += assignment: {:?}",
            diags
        );
    }

    #[test]
    fn variable_style_shovel_if_no_offense() {
        let diags = run_cop_full_with_config(
            &EndAlignment,
            b"      warnings << if initiator\n        t('msg')\n      else\n        t('other')\n      end\n",
            variable_config(),
        );
        assert!(
            diags.is_empty(),
            "variable style should align end with << receiver: {:?}",
            diags
        );
    }

    #[test]
    fn variable_style_case_method_arg_no_offense() {
        // RuboCop omakase / EnforcedStyleAlignWith: variable — yeah_dogg FP pattern.
        let diags = run_cop_full_with_config(
            &EndAlignment,
            b"def sorted(key = DEFAULT_SORT, shares: nil, pin: nil)\n  pin_first(case self.class.sort_key(key)\n  when \"name\" then 1\n  else 2\n  end, pin)\nend\n",
            variable_config(),
        );
        assert!(
            diags.is_empty(),
            "variable style should align end with call for case arg: {:?}",
            diags
        );
    }

    #[test]
    fn variable_style_case_command_arg_no_offense() {
        let diags = run_cop_full_with_config(
            &EndAlignment,
            b"test case a when b\nend\n",
            variable_config(),
        );
        assert!(
            diags.is_empty(),
            "variable style should align end with command for case arg: {:?}",
            diags
        );
    }

    #[test]
    fn variable_style_case_arg_keyword_aligned_is_offense() {
        let diags = run_cop_full_with_config(
            &EndAlignment,
            b"test case a when b\n end\n",
            variable_config(),
        );
        assert_eq!(
            diags.len(),
            1,
            "end aligned with case is bad under variable: {:?}",
            diags
        );
    }
}