selene_lib/lints/
restricted_module_paths.rs1use super::*;
2use crate::ast_util::name_paths::{name_path, name_path_from_prefix_suffix, take_while_keep_going};
3use std::collections::HashMap;
4use std::convert::Infallible;
5
6use full_moon::{
7 ast::{self, Ast},
8 visitors::Visitor,
9};
10use serde::Deserialize;
11
12#[derive(Clone, Default, Deserialize)]
13#[serde(default)]
14pub struct RestrictedModulePathsConfig {
15 pub restricted_paths: HashMap<String, String>,
16}
17
18pub struct RestrictedModulePathsLint {
19 config: RestrictedModulePathsConfig,
20}
21
22impl Lint for RestrictedModulePathsLint {
23 type Config = RestrictedModulePathsConfig;
24 type Error = Infallible;
25
26 const SEVERITY: Severity = Severity::Error;
27 const LINT_TYPE: LintType = LintType::Correctness;
28
29 fn new(config: Self::Config) -> Result<Self, Self::Error> {
30 Ok(RestrictedModulePathsLint { config })
31 }
32
33 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
34 if self.config.restricted_paths.is_empty() {
35 return Vec::new();
36 }
37
38 let mut visitor = RestrictedModulePathsVisitor {
39 restricted_paths: &self.config.restricted_paths,
40 violations: Vec::new(),
41 };
42
43 visitor.visit_ast(ast);
44
45 visitor.violations
46 }
47}
48
49struct RestrictedModulePathsVisitor<'a> {
50 restricted_paths: &'a HashMap<String, String>,
51 violations: Vec<Diagnostic>,
52}
53
54impl<'a> Visitor for RestrictedModulePathsVisitor<'a> {
55 fn visit_expression(&mut self, expression: &ast::Expression) {
56 self.check_expression(expression);
57 }
58
59 fn visit_function_call(&mut self, call: &ast::FunctionCall) {
60 let mut keep_going = true;
62 let suffixes: Vec<&ast::Suffix> = call
63 .suffixes()
64 .take_while(|suffix| take_while_keep_going(suffix, &mut keep_going))
65 .collect();
66
67 if let Some(path) = name_path_from_prefix_suffix(call.prefix(), suffixes.iter().copied()) {
68 let full_path = path.join(".");
69
70 let start_pos = call.prefix().start_position().unwrap();
72 let end_pos = if let Some(last_suffix) = suffixes.last() {
73 last_suffix.end_position().unwrap()
74 } else {
75 call.prefix().end_position().unwrap()
76 };
77
78 self.check_path_restriction(&full_path, (start_pos.bytes(), end_pos.bytes()));
79 }
80 }
81}
82
83impl<'a> RestrictedModulePathsVisitor<'a> {
84 fn check_expression(&mut self, expression: &ast::Expression) {
85 if let ast::Expression::Var(_) = expression {
87 if let Some(path) = name_path(expression) {
88 let full_path = path.join(".");
89 let range = expression.range().unwrap();
90 self.check_path_restriction(&full_path, (range.0.bytes(), range.1.bytes()));
91 }
92 }
93 }
94
95 fn check_path_restriction(&mut self, full_path: &str, range: (usize, usize)) {
96 if let Some(message) = self.restricted_paths.get(full_path) {
98 self.violations.push(Diagnostic::new_complete(
99 "restricted_module_paths",
100 format!("Module path `{full_path}` is restricted"),
101 Label::new((range.0 as u32, range.1 as u32)),
102 vec![message.clone()],
103 Vec::new(),
104 ));
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::{super::test_util::test_lint, *};
112 use std::collections::HashMap;
113
114 #[test]
115 fn test_restricted_module_paths() {
116 let mut restricted_paths = HashMap::new();
117 restricted_paths.insert(
118 "OldLibrary.Utils.deprecatedFunction".to_string(),
119 "OldLibrary.Utils.deprecatedFunction has been deprecated. Use NewLibrary.Utils.modernFunction instead.".to_string(),
120 );
121
122 test_lint(
123 RestrictedModulePathsLint::new(RestrictedModulePathsConfig { restricted_paths })
124 .unwrap(),
125 "restricted_module_paths",
126 "restricted_module_paths",
127 );
128 }
129}