selene_lib/lints/
roblox_manual_fromscale_or_fromoffset.rs1use super::*;
2use crate::ast_util::range;
3use std::convert::Infallible;
4
5use full_moon::{
6 ast::{self, Ast},
7 visitors::Visitor,
8};
9
10pub struct ManualFromScaleOrFromOffsetLint;
11
12fn create_diagnostic(args: &UDim2ComplexArgs) -> Diagnostic {
13 let code = "roblox_manual_fromscale_or_fromoffset";
14 let primary_label = Label::new(args.call_range);
15
16 match args.complexity_type {
17 UDim2ConstructorType::OffsetOnly => Diagnostic::new_complete(
18 code,
19 "this UDim2.new call only sets offset, and can be simplified using UDim2.fromOffset"
20 .to_owned(),
21 primary_label,
22 vec![format!(
23 "try: UDim2.fromOffset({}, {})",
24 args.arg_0, args.arg_1
25 )],
26 Vec::new(),
27 ),
28 UDim2ConstructorType::ScaleOnly => Diagnostic::new_complete(
29 code,
30 "this UDim2.new call only sets scale, and can be simplified using UDim2.fromScale"
31 .to_owned(),
32 primary_label,
33 vec![format!(
34 "try: UDim2.fromScale({}, {})",
35 args.arg_0, args.arg_1
36 )],
37 Vec::new(),
38 ),
39 }
40}
41
42impl Lint for ManualFromScaleOrFromOffsetLint {
43 type Config = ();
44 type Error = Infallible;
45
46 const SEVERITY: Severity = Severity::Warning;
47 const LINT_TYPE: LintType = LintType::Style;
48
49 fn new(_: Self::Config) -> Result<Self, Self::Error> {
50 Ok(ManualFromScaleOrFromOffsetLint)
51 }
52
53 fn pass(&self, ast: &Ast, context: &Context, _: &AstContext) -> Vec<Diagnostic> {
54 if !context.is_roblox() {
55 return Vec::new();
56 }
57
58 let mut visitor = UDim2NewVisitor::default();
59
60 visitor.visit_ast(ast);
61
62 visitor.args.iter().map(create_diagnostic).collect()
63 }
64}
65
66#[derive(Default)]
67struct UDim2NewVisitor {
68 args: Vec<UDim2ComplexArgs>,
69}
70
71#[derive(PartialEq)]
72enum UDim2ConstructorType {
73 ScaleOnly,
74 OffsetOnly,
75}
76
77struct UDim2ComplexArgs {
78 complexity_type: UDim2ConstructorType,
79 call_range: (usize, usize),
80 arg_0: String,
81 arg_1: String,
82}
83
84impl Visitor for UDim2NewVisitor {
85 fn visit_function_call(&mut self, call: &ast::FunctionCall) {
86 if_chain::if_chain! {
87 if let ast::Prefix::Name(token) = call.prefix();
88 if token.token().to_string() == "UDim2";
89 let mut suffixes = call.suffixes().collect::<Vec<_>>();
90
91 if suffixes.len() == 2; let call_suffix = suffixes.pop().unwrap();
93 let index_suffix = suffixes.pop().unwrap();
94
95 if let ast::Suffix::Index(ast::Index::Dot { name, .. }) = index_suffix;
96 if name.token().to_string() == "new";
97
98 if let ast::Suffix::Call(ast::Call::AnonymousCall(
99 ast::FunctionArgs::Parentheses { arguments, .. }
100 )) = call_suffix;
101
102 then {
103 if arguments.len() != 4 {
104 return;
105 }
106
107 let mut iter = arguments.iter();
108 let x_scale = iter.next().unwrap().to_string();
109 let x_offset = iter.next().unwrap().to_string();
110 let y_scale = iter.next().unwrap().to_string();
111 let y_offset = iter.next().unwrap().to_string();
112
113 let only_offset = x_scale.parse::<f32>() == Ok(0.0) && y_scale.parse::<f32>() == Ok(0.0);
114 let only_scale = x_offset.parse::<f32>() == Ok(0.0) && y_offset.parse::<f32>() == Ok(0.0);
115
116 if only_offset && only_scale
117 {
118 }
120 else if only_offset
121 {
122 self.args.push(UDim2ComplexArgs {
123 call_range: range(call),
124 arg_0: x_offset,
125 arg_1: y_offset,
126 complexity_type: UDim2ConstructorType::OffsetOnly,
127 });
128 }
129 else if only_scale
130 {
131 self.args.push(UDim2ComplexArgs {
132 call_range: range(call),
133 arg_0: x_scale,
134 arg_1: y_scale,
135 complexity_type: UDim2ConstructorType::ScaleOnly,
136 });
137 }
138 }
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::{super::test_util::test_lint, *};
146
147 #[test]
148 fn test_manual_fromscale_or_fromoffset() {
149 test_lint(
150 ManualFromScaleOrFromOffsetLint::new(()).unwrap(),
151 "roblox_manual_fromscale_or_fromoffset",
152 "roblox_manual_fromscale_or_fromoffset",
153 );
154 }
155}