Skip to content

Commit ac24987

Browse files
dtarassenkojvsqzj
authored andcommitted
Break on conditional expression (#23)
* Add LL3 rule for conditional expression breaking * Cover more test cases * Add ll3 to features.md * Add to example_lint_cfg
1 parent 4ed8568 commit ac24987

File tree

9 files changed

+178
-8
lines changed

9 files changed

+178
-8
lines changed

example_files/example_lint_cfg.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
"indent_continuation_line": {},
2222
"func_call_break_on_open_paren": {},
2323
"method_output_break": {},
24+
"conditional_expression_break_before_op": {},
2425
"annotate_lints": true
2526
}

src/analysis/parsing/expression.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::lint::{DMLStyleError,
2727
CurrentRules},
2828
AuxParams};
2929
use crate::lint::rules::indentation::IndentParenExprArgs;
30-
use crate::lint::rules::linebreaking::FuncCallBreakOnOpenParenArgs;
30+
use crate::lint::rules::linebreaking::{ConditionalExpressionBreakBeforeOperatorArgs, FuncCallBreakOnOpenParenArgs};
3131

3232
#[derive(Debug, Clone, PartialEq)]
3333
pub struct UnaryExpressionContent {
@@ -159,6 +159,8 @@ impl TreeElement for TertiaryExpressionContent {
159159
}
160160
fn evaluate_rules(&self, acc: &mut Vec<DMLStyleError>, rules: &CurrentRules, _aux: AuxParams) {
161161
rules.sp_ternary.check(SpTernaryArgs::from_tertiary_expression_content(self), acc);
162+
rules.conditional_expression_break_before_op
163+
.check(ConditionalExpressionBreakBeforeOperatorArgs::from_tertiary_expression(self), acc);
162164
}
163165
}
164166

src/lint/features.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Below are listed the currently supported rules for linting:
4747

4848
## Line Length
4949
- **LL1**, `long_lines`: Lines should be kept shorter than 80 characters. This limit can be set to a custom value
50+
- **LL3**, `conditional_expression_break`: Break conditional expressions before the ?, or both before the ? and before the :.
5051
- **LL5**, `method_output_break`: Break long method declarations with output parameters before the arrow.
5152
```
5253
method inquiry_status(uint64 physical_address)

src/lint/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
55
use std::str::FromStr;
66
use lazy_static::lazy_static;
77
use log::{debug, error, trace};
8-
use rules::linebreaking::{FuncCallBreakOnOpenParenOptions, MethodOutputBreakOptions};
8+
use rules::linebreaking::{FuncCallBreakOnOpenParenOptions, MethodOutputBreakOptions, ConditionalExpressionBreakBeforeOperatorOptions};
99
use serde::{Deserialize, Serialize};
1010
use regex::Regex;
1111
use rules::{instantiate_rules, CurrentRules, RuleType};
@@ -117,6 +117,8 @@ pub struct LintCfg {
117117
#[serde(default)]
118118
pub func_call_break_on_open_paren: Option<FuncCallBreakOnOpenParenOptions>,
119119
#[serde(default)]
120+
pub conditional_expression_break_before_op: Option<ConditionalExpressionBreakBeforeOperatorOptions>,
121+
#[serde(default)]
120122
pub method_output_break: Option<MethodOutputBreakOptions>,
121123
#[serde(default = "get_true")]
122124
pub annotate_lints: bool,
@@ -166,12 +168,12 @@ impl Default for LintCfg {
166168
indent_continuation_line: Some(IndentContinuationLineOptions{indentation_spaces: INDENTATION_LEVEL_DEFAULT}),
167169
func_call_break_on_open_paren: Some(FuncCallBreakOnOpenParenOptions{indentation_spaces: INDENTATION_LEVEL_DEFAULT}),
168170
method_output_break: Some(MethodOutputBreakOptions{}),
171+
conditional_expression_break_before_op: Some(ConditionalExpressionBreakBeforeOperatorOptions{}),
169172
annotate_lints: true,
170173
}
171174
}
172175
}
173176

174-
175177
#[derive(Debug, Clone)]
176178
pub struct DMLStyleError {
177179
pub error: LocalDMLError,

src/lint/rules/indentation.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,12 @@ impl IndentContinuationLineArgs {
731731
pub fn filter_out_last_semi_ranges(expression_tokens: &mut TreeElementTokenIterator) {
732732
// This function filters out the last semicolon in a list of tokens
733733
// as it is not part of the continuation line check.
734-
expression_tokens.pop_if(|token| token.kind == TokenKind::SemiColon);
734+
// expression_tokens.pop_if(|token| token.kind == TokenKind::SemiColon);
735+
if let Some(last_token) = expression_tokens.last() {
736+
if last_token.kind == TokenKind::SemiColon {
737+
expression_tokens.pop();
738+
}
739+
}
735740
}
736741

737742
pub fn from_statement_content(node: &StatementContent, depth: u32) -> Option<IndentContinuationLineArgs> {

src/lint/rules/linebreaking.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize};
22
use crate::analysis::parsing::lexer::TokenKind;
33
use crate::analysis::parsing::parser::Token;
44
use crate::analysis::parsing::structure::MethodContent;
5-
use crate::analysis::parsing::tree::{TreeElement, TreeElementTokenIterator, ZeroRange};
6-
use crate::analysis::parsing::expression::{CastContent, FunctionCallContent, ParenExpressionContent};
5+
use crate::analysis::parsing::tree::{ZeroRange, TreeElementTokenIterator, TreeElement};
6+
use crate::analysis::parsing::expression::{CastContent, FunctionCallContent,
7+
ParenExpressionContent, TertiaryExpressionContent};
78
use crate::lint::{DMLStyleError, RuleType};
89
use super::indentation::IndentParenExprArgs;
910
use super::Rule;
@@ -214,3 +215,74 @@ impl FuncCallBreakOnOpenParenRule {
214215
}
215216
}
216217
}
218+
219+
pub struct ConditionalExpressionBreakBeforeOperatorRule {
220+
pub enabled: bool,
221+
}
222+
223+
pub struct ConditionalExpressionBreakBeforeOperatorArgs {
224+
pub left: ZeroRange,
225+
pub left_operation: ZeroRange,
226+
pub middle: ZeroRange,
227+
pub right_operation: ZeroRange,
228+
pub right: ZeroRange
229+
}
230+
231+
232+
impl ConditionalExpressionBreakBeforeOperatorArgs {
233+
pub fn from_tertiary_expression(node: &TertiaryExpressionContent)
234+
-> Option<ConditionalExpressionBreakBeforeOperatorArgs> {
235+
Some(ConditionalExpressionBreakBeforeOperatorArgs {
236+
left: node.left.range(),
237+
left_operation: node.left_operation.range(),
238+
middle: node.middle.range(),
239+
right_operation: node.right_operation.range(),
240+
right: node.right.range(),
241+
})
242+
}
243+
}
244+
245+
impl ConditionalExpressionBreakBeforeOperatorRule {
246+
pub fn from_options(options: &Option<ConditionalExpressionBreakBeforeOperatorOptions>) -> ConditionalExpressionBreakBeforeOperatorRule {
247+
match options {
248+
Some(_options) => ConditionalExpressionBreakBeforeOperatorRule {
249+
enabled: true,
250+
},
251+
None => ConditionalExpressionBreakBeforeOperatorRule {
252+
enabled: false,
253+
}
254+
}
255+
}
256+
257+
pub fn check(&self, args: Option<ConditionalExpressionBreakBeforeOperatorArgs>, acc: &mut Vec<DMLStyleError>) {
258+
if !self.enabled { return; }
259+
let Some(args) = args else { return; };
260+
let has_break_before_question_operator = args.left.row_end.0 != args.left_operation.row_start.0;
261+
let has_break_after_question_operator = args.left_operation.row_end.0 != args.middle.row_start.0;
262+
let has_break_before_colon_operator = args.middle.row_end.0 != args.right_operation.row_start.0;
263+
let has_break_after_colon_operator = args.right_operation.row_end.0 != args.right.row_start.0;
264+
if has_break_after_question_operator {
265+
acc.push(self.create_err(args.left_operation));
266+
}
267+
if has_break_after_colon_operator || (has_break_before_colon_operator && !has_break_before_question_operator ){
268+
acc.push(self.create_err(args.right_operation));
269+
}
270+
}
271+
}
272+
273+
274+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
275+
pub struct ConditionalExpressionBreakBeforeOperatorOptions{
276+
}
277+
278+
impl Rule for ConditionalExpressionBreakBeforeOperatorRule {
279+
fn name() -> &'static str {
280+
"COND_EXPRESSION_BREAK_BEFORE_OPERATOR"
281+
}
282+
fn description() -> &'static str {
283+
"Break conditional expressions before the ?, or both before the ? and before the :."
284+
}
285+
fn get_rule_type() -> RuleType {
286+
RuleType::LL3
287+
}
288+
}

src/lint/rules/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use indentation::{LongLinesRule,
2525
IndentEmptyLoopRule,
2626
IndentContinuationLineRule};
2727
use linebreaking::{FuncCallBreakOnOpenParenRule,
28-
MethodOutputBreakRule};
28+
MethodOutputBreakRule,
29+
ConditionalExpressionBreakBeforeOperatorRule};
2930
use crate::lint::{LintCfg, DMLStyleError};
3031
use crate::analysis::{LocalDMLError, parsing::tree::ZeroRange};
3132

@@ -51,6 +52,7 @@ pub struct CurrentRules {
5152
pub indent_continuation_line: IndentContinuationLineRule,
5253
pub func_call_break_on_open_paren: FuncCallBreakOnOpenParenRule,
5354
pub method_output_break: MethodOutputBreakRule,
55+
pub conditional_expression_break_before_op: ConditionalExpressionBreakBeforeOperatorRule,
5456
}
5557

5658
pub fn instantiate_rules(cfg: &LintCfg) -> CurrentRules {
@@ -76,6 +78,7 @@ pub fn instantiate_rules(cfg: &LintCfg) -> CurrentRules {
7678
indent_continuation_line: IndentContinuationLineRule::from_options(&cfg.indent_continuation_line),
7779
func_call_break_on_open_paren: FuncCallBreakOnOpenParenRule::from_options(&cfg.func_call_break_on_open_paren),
7880
method_output_break: MethodOutputBreakRule { enabled: cfg.method_output_break.is_some() },
81+
conditional_expression_break_before_op: ConditionalExpressionBreakBeforeOperatorRule::from_options(&cfg.conditional_expression_break_before_op),
7982
}
8083
}
8184

@@ -110,6 +113,7 @@ pub enum RuleType {
110113
NspUnary,
111114
NspTrailing,
112115
LL1,
116+
LL3,
113117
LL5,
114118
LL6,
115119
IN2,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::lint::rules::tests::common::{set_up, assert_snippet};
2+
use crate::lint::rules::RuleType;
3+
4+
5+
static CORRECT_BREAK_BEFORE_QUESTION_MARK: &str = "
6+
method bootprep_type_to_string(uint8 prep_type) -> (const char*) {
7+
return
8+
prep_type == PREP_GENERAL
9+
? \"PrepGeneral\" : \"PrepEarly\";
10+
}";
11+
#[test]
12+
fn condexpr_correct_break_before_question_mark() {
13+
let rules = set_up();
14+
let expected_errors = vec![];
15+
assert_snippet(CORRECT_BREAK_BEFORE_QUESTION_MARK, expected_errors, &rules);
16+
}
17+
18+
static CORRECT_BREAK_BEFORE_COLON_AND_QUESTION_MARK: &str = "
19+
method bootprep_type_to_string(uint8 prep_type) -> (const char*) {
20+
return
21+
prep_type == PREP_GENERAL
22+
? \"PrepGeneral\"
23+
: \"PrepEarly\";
24+
}";
25+
#[test]
26+
fn condexpr_correct_break_after_colon_and_question_mark() {
27+
let rules = set_up();
28+
let expected_errors = vec![];
29+
assert_snippet(CORRECT_BREAK_BEFORE_COLON_AND_QUESTION_MARK, expected_errors, &rules);
30+
}
31+
32+
static BREAK_ONLY_BEFORE_COLON: &str = "
33+
method harvest_resource() {
34+
harvest_resource(my->gas < GAS_THRESHOLD ? Rsrc_Gas
35+
: (my->minerals < MINERAL_THRESHOLD
36+
? Rsrc_Minerals : nearest_resource()));
37+
}";
38+
#[test]
39+
fn condexpr_only_before_colon() {
40+
let rules = set_up();
41+
let expected_errors = define_expected_errors!(
42+
RuleType::LL3,
43+
(3, 3, 21, 22),
44+
);
45+
assert_snippet(BREAK_ONLY_BEFORE_COLON, expected_errors, &rules);
46+
}
47+
48+
static CORRECT_NESTED: &str = "
49+
method bootprep_type_to_string(uint8 prep_type) -> (const char*) {
50+
return
51+
prep_type == PREP_GENERAL
52+
? \"PrepGeneral\"
53+
: prep_type == PREP_EARLY
54+
? \"PrepEarly\" : \"UnknownBootPrepType\";
55+
}";
56+
#[test]
57+
fn condexpr_correct_nested() {
58+
let rules = set_up();
59+
let expected_errors = vec![];
60+
assert_snippet(CORRECT_NESTED, expected_errors, &rules);
61+
}
62+
63+
static BREAK_AFTER_OPERATORS_NESTED: &str = "
64+
method bootprep_type_to_string(uint8 prep_type) -> (const char*) {
65+
return
66+
prep_type == PREP_GENERAL ?
67+
\"PrepGeneral\" :
68+
prep_type == PREP_EARLY ? \"PrepEarly\" :
69+
\"UnknownBootPrepType\";
70+
}";
71+
#[test]
72+
fn condexpr_broken_after_operators_nested() {
73+
let rules = set_up();
74+
let expected_errors = define_expected_errors!(
75+
RuleType::LL3,
76+
(3, 3, 34, 35),
77+
(4, 4, 22, 23),
78+
(5, 5, 46, 47),
79+
);
80+
assert_snippet(BREAK_AFTER_OPERATORS_NESTED, expected_errors, &rules);
81+
}
82+
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
mod func_call_break_open_paren;
2-
mod method_output_break;
2+
mod method_output_break;
3+
mod conditional_expression_break;

0 commit comments

Comments
 (0)