Skip to content

Commit abd5522

Browse files
committed
lint ForbidKeysRemoveCall
1 parent dfbed6b commit abd5522

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ fn main() {
6060
};
6161

6262
track_lint(ForbidAsPrimitiveConversion::lint(&parsed_file));
63+
track_lint(ForbidKeysRemoveCall::lint(&parsed_file));
6364
track_lint(RequireFreezeStruct::lint(&parsed_file));
6465
track_lint(RequireExplicitPalletIndex::lint(&parsed_file));
6566
});
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use super::*;
2+
use syn::{
3+
punctuated::Punctuated, spanned::Spanned, token::Comma, visit::Visit, Expr, ExprCall, ExprPath,
4+
File,
5+
};
6+
7+
pub struct ForbidKeysRemoveCall;
8+
9+
impl Lint for ForbidKeysRemoveCall {
10+
fn lint(source: &File) -> Result {
11+
let mut visitor = KeysRemoveVisitor::default();
12+
visitor.visit_file(source);
13+
14+
if visitor.errors.is_empty() {
15+
Ok(())
16+
} else {
17+
Err(visitor.errors)
18+
}
19+
}
20+
}
21+
22+
#[derive(Default)]
23+
struct KeysRemoveVisitor {
24+
errors: Vec<syn::Error>,
25+
}
26+
27+
impl<'ast> Visit<'ast> for KeysRemoveVisitor {
28+
fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
29+
let ExprCall { func, args, .. } = node;
30+
if is_keys_remove_call(func, args) {
31+
let msg = "Keys::<T>::remove()` is banned to prevent accidentally breaking \
32+
the neuron sequence. If you need to replace neuron, try `SubtensorModule::replace_neuron()`";
33+
self.errors.push(syn::Error::new(node.func.span(), msg));
34+
}
35+
}
36+
}
37+
38+
fn is_keys_remove_call(func: &Expr, args: &Punctuated<Expr, Comma>) -> bool {
39+
let Expr::Path(ExprPath { path, .. }) = func else {
40+
return false;
41+
};
42+
let func = &path.segments;
43+
if func.len() != 2 || args.len() != 2 {
44+
return false;
45+
}
46+
47+
func[0].ident == "Keys"
48+
&& !func[0].arguments.is_none()
49+
&& func[1].ident == "remove"
50+
&& func[1].arguments.is_none()
51+
}
52+
53+
#[cfg(test)]
54+
mod tests {
55+
use super::*;
56+
57+
fn lint(input: &str) -> Result {
58+
let mut visitor = KeysRemoveVisitor::default();
59+
visitor
60+
.visit_expr_call(&syn::parse_str(input).expect("should only use on a function call"));
61+
62+
if visitor.errors.is_empty() {
63+
Ok(())
64+
} else {
65+
Err(visitor.errors)
66+
}
67+
}
68+
69+
#[test]
70+
fn test_keys_remove_forbidden() {
71+
let input = r#"Keys::<T>::remove(netuid, uid_to_replace)"#;
72+
assert!(lint(input).is_err());
73+
let input = r#"Keys::<U>::remove(netuid, uid_to_replace)"#;
74+
assert!(lint(input).is_err());
75+
let input = r#"Keys::<U>::remove(1, "2".parse().unwrap(),)"#;
76+
assert!(lint(input).is_err());
77+
}
78+
79+
#[test]
80+
fn test_non_keys_remove_not_forbidden() {
81+
let input = r#"remove(netuid, uid_to_replace)"#;
82+
assert!(lint(input).is_ok());
83+
let input = r#"Keys::remove(netuid, uid_to_replace)"#;
84+
assert!(lint(input).is_ok());
85+
let input = r#"Keys::<T>::remove::<U>(netuid, uid_to_replace)"#;
86+
assert!(lint(input).is_ok());
87+
let input = r#"Keys::<T>::remove(netuid, uid_to_replace, third_wheel)"#;
88+
assert!(lint(input).is_ok());
89+
let input = r#"ParentKeys::remove(netuid, uid_to_replace)"#;
90+
assert!(lint(input).is_ok());
91+
let input = r#"ChildKeys::<T>::remove(netuid, uid_to_replace)"#;
92+
assert!(lint(input).is_ok());
93+
}
94+
}

support/linting/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ pub mod lint;
22
pub use lint::*;
33

44
mod forbid_as_primitive;
5+
mod forbid_keys_remove;
56
mod pallet_index;
67
mod require_freeze_struct;
78

89
pub use forbid_as_primitive::ForbidAsPrimitiveConversion;
10+
pub use forbid_keys_remove::ForbidKeysRemoveCall;
911
pub use pallet_index::RequireExplicitPalletIndex;
1012
pub use require_freeze_struct::RequireFreezeStruct;

0 commit comments

Comments
 (0)