diff --git a/example_files/example_lint_cfg.json b/example_files/example_lint_cfg.json index 6ebd67b..9b5daf8 100644 --- a/example_files/example_lint_cfg.json +++ b/example_files/example_lint_cfg.json @@ -7,4 +7,5 @@ "nsp_trailing": {}, "long_lines": { "max_length": 80 }, "in3": { "indentation_spaces": 4 }, + "in9": { "indentation_spaces": 4 }, } diff --git a/src/analysis/parsing/statement.rs b/src/analysis/parsing/statement.rs index 0c4e80b..0cc938d 100644 --- a/src/analysis/parsing/statement.rs +++ b/src/analysis/parsing/statement.rs @@ -22,7 +22,7 @@ use crate::analysis::parsing::misc::{Initializer, InitializerContent, CDecl, use crate::analysis::parsing::structure::{parse_vardecl, VarDecl}; use crate::analysis::LocalDMLError; use crate::lint::rules::{CurrentRules, - indentation::IN3Args, + indentation::{IN3Args, IN9Args}, spacing::{NspInparenArgs, SpBracesArgs, SpPunctArgs}, @@ -1003,7 +1003,8 @@ impl TreeElement for SwitchCase { Self::Default(default, colon) => create_subs!(default, colon), } } - fn evaluate_rules(&self, acc: &mut Vec, rules: &CurrentRules, _depth: &mut u32) { + fn evaluate_rules(&self, acc: &mut Vec, rules: &CurrentRules, depth: &mut u32) { + rules.in9.check(acc, IN9Args::from_switch_case(self, depth)); } } diff --git a/src/lint/mod.rs b/src/lint/mod.rs index 164ef7d..9e51b32 100644 --- a/src/lint/mod.rs +++ b/src/lint/mod.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use rules::{instantiate_rules, CurrentRules}; use rules::{spacing::{SpBraceOptions, SpPunctOptions, NspFunparOptions, NspInparenOptions, NspUnaryOptions, NspTrailingOptions}, - indentation::{LongLineOptions, IN3Options}, + indentation::{LongLineOptions, IN3Options, IN9Options}, }; use crate::analysis::{DMLError, IsolatedAnalysis, LocalDMLError}; use crate::analysis::parsing::tree::TreeElement; @@ -54,6 +54,8 @@ pub struct LintCfg { pub long_lines: Option, #[serde(default)] pub in3: Option, + #[serde(default)] + pub in9: Option, } impl Default for LintCfg { @@ -69,6 +71,7 @@ impl Default for LintCfg { max_length: MAX_LENGTH_DEFAULT, }), in3: Some(IN3Options{indentation_spaces: 4}), + in9: Some(IN9Options{indentation_spaces: 4}), } } } diff --git a/src/lint/rules/indentation.rs b/src/lint/rules/indentation.rs index a5b5b7c..0ff6fc1 100644 --- a/src/lint/rules/indentation.rs +++ b/src/lint/rules/indentation.rs @@ -1,11 +1,11 @@ use std::convert::TryInto; -use crate::analysis::parsing::{statement::CompoundContent, +use crate::analysis::parsing::{statement::{self, CompoundContent, SwitchCase}, structure::ObjectStatementsContent, types::{LayoutContent, StructTypeContent}}; use crate::span::{Range, ZeroIndexed, Row, Column}; use crate::analysis::LocalDMLError; -use crate::analysis::parsing::tree::{ZeroRange, TreeElement}; +use crate::analysis::parsing::tree::{ZeroRange, Content, TreeElement}; use serde::{Deserialize, Serialize}; use super::Rule; @@ -161,6 +161,90 @@ impl Rule for IN3Rule { } } +pub struct IN9Rule { + pub enabled: bool, + indentation_spaces: u32 +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct IN9Options { + pub indentation_spaces: u32, +} +pub struct IN9Args<'a> { + case_range: ZeroRange, + expected_depth: &'a mut u32, +} + +impl IN9Args<'_> { + pub fn from_switch_case<'a>(node: &SwitchCase, depth: &'a mut u32) -> Option> { + match node { + SwitchCase::Case(_, _, _) | + SwitchCase::Default(_, _) => {}, + SwitchCase::Statement(statement) => { + if let Content::Some(ref content) = *statement.content { + if let statement::StatementContent::Compound(_) = content { + return None; + } + *depth += 1; + } + }, + SwitchCase::HashIf(_) => { + return None; + } + } + + Some(IN9Args { + case_range: node.range(), + expected_depth: depth + }) + + } +} + +impl IN9Rule { + pub fn from_options(options: &Option) -> IN9Rule { + match options { + Some(options) => IN9Rule { + enabled: true, + indentation_spaces: options.indentation_spaces + }, + None => IN9Rule { + enabled: false, + indentation_spaces: 0 + } + } + } + pub fn check<'a>(&self, acc: &mut Vec, + args: Option>) + { + if !self.enabled { return; } + let Some(args) = args else { return; }; + if self.indentation_is_not_aligned(args.case_range, *args.expected_depth) { + let dmlerror = LocalDMLError { + range: args.case_range, + description: Self::description().to_string(), + }; + acc.push(dmlerror); + } + } + fn indentation_is_not_aligned(&self, member_range: ZeroRange, depth: u32) -> bool { + // Implicit IN1 + let expected_column = self.indentation_spaces * depth; + print!("{:#?}", expected_column); + member_range.col_start.0 != expected_column + } +} + +impl Rule for IN9Rule { + fn name() -> &'static str { + "IN9" + } + fn description() -> &'static str { + "Case labels are indented one level less than surrounding lines, \ + so that they are on the same level as the switch statement" + } +} + #[cfg(test)] pub mod tests { diff --git a/src/lint/rules/mod.rs b/src/lint/rules/mod.rs index 8549afb..591524a 100644 --- a/src/lint/rules/mod.rs +++ b/src/lint/rules/mod.rs @@ -5,7 +5,7 @@ pub mod test; use spacing::{SpBracesRule, SpPunctRule, NspFunparRule, NspInparenRule, NspUnaryRule, NspTrailingRule}; -use indentation::{LongLinesRule, IN3Rule}; +use indentation::{LongLinesRule, IN3Rule, IN9Rule}; use crate::lint::LintCfg; pub struct CurrentRules { @@ -17,6 +17,7 @@ pub struct CurrentRules { pub nsp_trailing: NspTrailingRule, pub long_lines: LongLinesRule, pub in3: IN3Rule, + pub in9: IN9Rule } pub fn instantiate_rules(cfg: &LintCfg) -> CurrentRules { @@ -29,6 +30,7 @@ pub fn instantiate_rules(cfg: &LintCfg) -> CurrentRules { nsp_trailing: NspTrailingRule { enabled: cfg.nsp_trailing.is_some() }, long_lines: LongLinesRule::from_options(&cfg.long_lines), in3: IN3Rule::from_options(&cfg.in3), + in9: IN9Rule::from_options(&cfg.in9), } } diff --git a/src/lint/rules/test/mod.rs b/src/lint/rules/test/mod.rs index 2b5cfcb..b67d78b 100644 --- a/src/lint/rules/test/mod.rs +++ b/src/lint/rules/test/mod.rs @@ -168,5 +168,49 @@ fn in3_cond_structure_bad_indent() { assert_indentation(IN3_COND_STRUCTURE_BAD_INDENT, 4, rules); } +pub static IN9_CORRECT_CASE_INDENT: &str = " +method some_switch(int arg) { + switch(arg) { + case ZERO: +#if (asdd == 0) { + some_call(); +} + if (a) { + return; + } + some_call(); + break; + default: { return; } + } +} +"; + +pub static IN9_INCORRECT_CASE_INDENT: &str = " +method some_switch(int arg) { + switch(arg) { + case ZERO: +#if (asdd == 0) { + some_call(); +} + if (a) { + return; + } + some_call(); + break; + case ONE: { + return; + } + default: { return; } + } +} +"; + +#[test] +// #[ignore] +fn in9_correct_case_indent() { + let rules = set_up(); + assert_snippet(IN9_CORRECT_CASE_INDENT, 0, &rules); + assert_snippet(IN9_INCORRECT_CASE_INDENT, 4, &rules); +} }