1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use crate::rule_prelude::*;
use ast::{BinExpr, BinOp, Expr, UnaryOp};
use rslint_parser::{TextRange, TextSize};
use SyntaxKind::*;
declare_lint! {
#[derive(Default)]
NoUnsafeNegation,
errors,
"no-unsafe-negation"
}
#[typetag::serde]
impl CstRule for NoUnsafeNegation {
fn check_node(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
if node.kind() == BIN_EXPR
&& matches!(node.to::<BinExpr>().op()?, BinOp::Instanceof | BinOp::In)
{
let expr = node.to::<BinExpr>();
if let Expr::UnaryExpr(unary) = expr.lhs()? {
if unary.op()? == UnaryOp::LogicalNot {
let unary_node = unary.expr()?.syntax().clone();
let no_op_text = &node.trimmed_text().to_string()[1..];
let mut eq_expr = format!("(!{}", no_op_text);
eq_expr.insert(usize::from(unary_node.trimmed_text().len()) + 2, ')');
let rest_range = TextRange::new(
node.trimmed_range().start() + TextSize::from(1),
node.trimmed_range().end(),
);
let err = ctx
.err(
self.name(),
"Unsafe negation of a value in a binary expression",
)
.primary(
unary.op_token().unwrap(),
format!(
"precedence makes this expression equivalent to `{}`",
eq_expr
),
)
.secondary(rest_range, "`!` is not negating this expression")
.suggestion_with_labels(
expr.range(),
"wrap the instanceof check in parentheses",
format!("!({})", no_op_text),
Applicability::MaybeIncorrect,
vec![1..2, no_op_text.len() + 2..no_op_text.len() + 3],
);
ctx.fix().wrap(node.add_start(1), Wrapping::Parens);
ctx.add_err(err);
}
}
}
None
}
}
rule_tests! {
NoUnsafeNegation::default(),
err: {
"!foo in bar",
"![5] instanceof !4",
"!!!!!instanceof !!foo instanceof !!bar"
},
ok: {
"(!foo) instanceof bar",
"key in bar",
"bar instanceof bar",
"1 in [1, 1, 1, ((!1) in [1111111111, 111])]"
}
}