diff --git a/selene-lib/src/lint_filtering.rs b/selene-lib/src/lint_filtering.rs index 57ae87b7..e814e9d3 100644 --- a/selene-lib/src/lint_filtering.rs +++ b/selene-lib/src/lint_filtering.rs @@ -4,7 +4,7 @@ use crate::{ visit_nodes::{NodeVisitor, VisitorType}, }, lint_exists, - lints::{Diagnostic, Label, Severity}, + lints::{Applicability, Diagnostic, Label, Severity}, CheckerDiagnostic, LintVariation, }; use full_moon::{ast::Ast, node::Node, tokenizer::TokenType}; @@ -150,6 +150,7 @@ impl NodeVisitor for FilterVisitor { trivia_end_position.bytes(), )), None, + Applicability::Unspecified, )) } })); @@ -225,6 +226,7 @@ pub fn filter_diagnostics( "global filter must be before this".to_owned(), )], None, + Applicability::Unspecified, )); continue; @@ -247,6 +249,7 @@ pub fn filter_diagnostics( "conflicts with this".to_owned(), )], None, + Applicability::Unspecified, )); } } diff --git a/selene-lib/src/lints.rs b/selene-lib/src/lints.rs index 45578caa..1189071d 100644 --- a/selene-lib/src/lints.rs +++ b/selene-lib/src/lints.rs @@ -89,6 +89,27 @@ pub enum Severity { Warning, } +/// Indicates the confidence in the correctness of a suggestion +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Applicability { + /// The suggestion is definitely what the user intended, or maintains the exact meaning of the code + /// + /// The suggestion is safe to be automatically applied + MachineApplicable, + + /// The suggestion is probably what the user intended, but may not maintain the exact meaning of the code + /// + /// The suggestion generates valid code when applied + MaybeIncorrect, + + /// The suggestion is probably what the user intended, but may not maintain the exact meaning of the code + /// + /// The suggestion does not generate valid code when applied + HasPlaceholders, + + Unspecified, +} + #[derive(Debug)] pub struct Diagnostic { pub code: &'static str, @@ -97,6 +118,7 @@ pub struct Diagnostic { pub primary_label: Label, pub secondary_labels: Vec<Label>, pub fixed_code: Option<String>, + pub applicability: Applicability, } impl Diagnostic { @@ -105,12 +127,14 @@ impl Diagnostic { message: String, primary_label: Label, fixed_code: Option<String>, + applicability: Applicability, ) -> Self { Self { code, message, primary_label, fixed_code, + applicability, notes: Vec::new(), secondary_labels: Vec::new(), @@ -124,6 +148,7 @@ impl Diagnostic { notes: Vec<String>, secondary_labels: Vec<Label>, fixed_code: Option<String>, + applicability: Applicability, ) -> Self { Self { code, @@ -132,6 +157,7 @@ impl Diagnostic { primary_label, secondary_labels, fixed_code, + applicability, } } diff --git a/selene-lib/src/lints/almost_swapped.rs b/selene-lib/src/lints/almost_swapped.rs index 3376d9b4..10c81372 100644 --- a/selene-lib/src/lints/almost_swapped.rs +++ b/selene-lib/src/lints/almost_swapped.rs @@ -46,6 +46,7 @@ impl Lint for AlmostSwappedLint { )], Vec::new(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/bad_string_escape.rs b/selene-lib/src/lints/bad_string_escape.rs index 001f5c7e..55c8cae5 100644 --- a/selene-lib/src/lints/bad_string_escape.rs +++ b/selene-lib/src/lints/bad_string_escape.rs @@ -51,12 +51,14 @@ impl Lint for BadStringEscapeLint { "string escape sequence doesn't exist".to_owned(), Label::new(sequence.range.to_owned()), None, + Applicability::Unspecified, ), ReasonWhy::Malformed => Diagnostic::new( "bad_string_escape", "string escape sequence is malformed".to_owned(), Label::new(sequence.range.to_owned()), None, + Applicability::Unspecified, ), ReasonWhy::DecimalTooHigh => Diagnostic::new_complete( "bad_string_escape", @@ -68,6 +70,7 @@ impl Lint for BadStringEscapeLint { ], Vec::new(), None, + Applicability::Unspecified, ), ReasonWhy::CodepointTooHigh => Diagnostic::new_complete( "bad_string_escape", @@ -79,6 +82,7 @@ impl Lint for BadStringEscapeLint { ], Vec::new(), None, + Applicability::Unspecified, ), ReasonWhy::DoubleInSingle => Diagnostic::new( "bad_string_escape", @@ -86,6 +90,7 @@ impl Lint for BadStringEscapeLint { .to_owned(), Label::new(sequence.range.to_owned()), None, + Applicability::Unspecified, ), ReasonWhy::SingleInDouble => Diagnostic::new( "bad_string_escape", @@ -93,6 +98,7 @@ impl Lint for BadStringEscapeLint { .to_owned(), Label::new(sequence.range.to_owned()), None, + Applicability::Unspecified, ), }) .collect() diff --git a/selene-lib/src/lints/compare_nan.rs b/selene-lib/src/lints/compare_nan.rs index cceda680..9b05cb2f 100644 --- a/selene-lib/src/lints/compare_nan.rs +++ b/selene-lib/src/lints/compare_nan.rs @@ -43,6 +43,7 @@ impl Lint for CompareNanLint { vec![format!("try: `{}` instead", fixed_code.clone())], Vec::new(), Some(fixed_code), + Applicability::MaybeIncorrect, ) }) .collect() diff --git a/selene-lib/src/lints/constant_table_comparison.rs b/selene-lib/src/lints/constant_table_comparison.rs index 112e9f5c..b19cb53a 100644 --- a/selene-lib/src/lints/constant_table_comparison.rs +++ b/selene-lib/src/lints/constant_table_comparison.rs @@ -60,6 +60,7 @@ impl Lint for ConstantTableComparisonLint { }, Vec::new(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/deprecated.rs b/selene-lib/src/lints/deprecated.rs index 9a3343c7..95d01f38 100644 --- a/selene-lib/src/lints/deprecated.rs +++ b/selene-lib/src/lints/deprecated.rs @@ -132,6 +132,7 @@ impl<'a> DeprecatedVisitor<'a> { notes, Vec::new(), fixed_code, + Applicability::MachineApplicable, )); } } diff --git a/selene-lib/src/lints/divide_by_zero.rs b/selene-lib/src/lints/divide_by_zero.rs index 50ce7c4a..65a81c9d 100644 --- a/selene-lib/src/lints/divide_by_zero.rs +++ b/selene-lib/src/lints/divide_by_zero.rs @@ -36,6 +36,7 @@ impl Lint for DivideByZeroLint { "dividing by zero is not allowed, use math.huge instead".to_owned(), Label::new(*position), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/duplicate_keys.rs b/selene-lib/src/lints/duplicate_keys.rs index 5d161f81..015b3f3c 100644 --- a/selene-lib/src/lints/duplicate_keys.rs +++ b/selene-lib/src/lints/duplicate_keys.rs @@ -43,6 +43,7 @@ impl Lint for DuplicateKeysLint { format!("`{}` originally declared here", duplicate.name), )], None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/empty_if.rs b/selene-lib/src/lints/empty_if.rs index 2f06add8..df59d5d7 100644 --- a/selene-lib/src/lints/empty_if.rs +++ b/selene-lib/src/lints/empty_if.rs @@ -64,6 +64,7 @@ impl Lint for EmptyIfLint { .to_owned(), Label::new(position.0), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/empty_loop.rs b/selene-lib/src/lints/empty_loop.rs index 45aff668..d793059e 100644 --- a/selene-lib/src/lints/empty_loop.rs +++ b/selene-lib/src/lints/empty_loop.rs @@ -59,6 +59,7 @@ impl Lint for EmptyLoopLint { "empty loop block".to_owned(), Label::new(position), Some(String::new()), + Applicability::MaybeIncorrect, ) }) .collect() diff --git a/selene-lib/src/lints/global_usage.rs b/selene-lib/src/lints/global_usage.rs index f35984cd..9d5158c1 100644 --- a/selene-lib/src/lints/global_usage.rs +++ b/selene-lib/src/lints/global_usage.rs @@ -77,6 +77,7 @@ impl Lint for GlobalLint { ), Label::new(reference.identifier), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/high_cyclomatic_complexity.rs b/selene-lib/src/lints/high_cyclomatic_complexity.rs index 0dfa1a21..095f7930 100644 --- a/selene-lib/src/lints/high_cyclomatic_complexity.rs +++ b/selene-lib/src/lints/high_cyclomatic_complexity.rs @@ -59,6 +59,7 @@ impl Lint for HighCyclomaticComplexityLint { ), Label::new(position), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/if_same_then_else.rs b/selene-lib/src/lints/if_same_then_else.rs index 32fb3305..8486490f 100644 --- a/selene-lib/src/lints/if_same_then_else.rs +++ b/selene-lib/src/lints/if_same_then_else.rs @@ -42,6 +42,7 @@ impl Lint for IfSameThenElseLint { "note: same as this".to_owned(), )], None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/ifs_same_cond.rs b/selene-lib/src/lints/ifs_same_cond.rs index 96aafc0d..dd40bb0d 100644 --- a/selene-lib/src/lints/ifs_same_cond.rs +++ b/selene-lib/src/lints/ifs_same_cond.rs @@ -42,6 +42,7 @@ impl Lint for IfsSameCondLint { "note: same as this".to_owned(), )], None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/manual_table_clone.rs b/selene-lib/src/lints/manual_table_clone.rs index 2af4dbba..f2dc5e16 100644 --- a/selene-lib/src/lints/manual_table_clone.rs +++ b/selene-lib/src/lints/manual_table_clone.rs @@ -85,6 +85,7 @@ impl ManualTableCloneMatch { Vec::new() }, None, + Applicability::Unspecified, ) } } diff --git a/selene-lib/src/lints/mismatched_arg_count.rs b/selene-lib/src/lints/mismatched_arg_count.rs index 2efdc13d..43ccd8f7 100644 --- a/selene-lib/src/lints/mismatched_arg_count.rs +++ b/selene-lib/src/lints/mismatched_arg_count.rs @@ -73,6 +73,7 @@ impl Lint for MismatchedArgCountLint { }) .collect(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/mixed_table.rs b/selene-lib/src/lints/mixed_table.rs index 2ce691cb..d53b065c 100644 --- a/selene-lib/src/lints/mixed_table.rs +++ b/selene-lib/src/lints/mixed_table.rs @@ -35,6 +35,7 @@ impl Lint for MixedTableLint { vec!["help: change this table to either an array or dictionary".to_owned()], Vec::new(), None, + Applicability::Unspecified, )); } diff --git a/selene-lib/src/lints/multiple_statements.rs b/selene-lib/src/lints/multiple_statements.rs index 8d319b67..858bd3bf 100644 --- a/selene-lib/src/lints/multiple_statements.rs +++ b/selene-lib/src/lints/multiple_statements.rs @@ -54,6 +54,7 @@ impl Lint for MultipleStatementsLint { "only one statement per line is allowed".to_owned(), Label::new(*position), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/must_use.rs b/selene-lib/src/lints/must_use.rs index 01c1bff0..5a01f19e 100644 --- a/selene-lib/src/lints/must_use.rs +++ b/selene-lib/src/lints/must_use.rs @@ -49,6 +49,7 @@ impl Lint for MustUseLint { ), Label::new(function_call_stmt.call_prefix_range), None, + Applicability::Unspecified, )); } diff --git a/selene-lib/src/lints/parenthese_conditions.rs b/selene-lib/src/lints/parenthese_conditions.rs index 8eb87aef..f8fae46f 100644 --- a/selene-lib/src/lints/parenthese_conditions.rs +++ b/selene-lib/src/lints/parenthese_conditions.rs @@ -36,6 +36,7 @@ impl Lint for ParentheseConditionsLint { "lua does not require parentheses around conditions".to_owned(), Label::new(*position), Some(context.code[position.0 + 1..position.1 - 1].to_string()), + Applicability::MachineApplicable, ) }) .collect() diff --git a/selene-lib/src/lints/roblox_incorrect_color3_new_bounds.rs b/selene-lib/src/lints/roblox_incorrect_color3_new_bounds.rs index de4e02d7..8e82c35b 100644 --- a/selene-lib/src/lints/roblox_incorrect_color3_new_bounds.rs +++ b/selene-lib/src/lints/roblox_incorrect_color3_new_bounds.rs @@ -40,6 +40,7 @@ impl Lint for Color3BoundsLint { vec!["help: did you mean to use Color3.fromRGB instead?".to_owned()], Vec::new(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/roblox_incorrect_roact_usage.rs b/selene-lib/src/lints/roblox_incorrect_roact_usage.rs index a4118fbc..6aae7cb1 100644 --- a/selene-lib/src/lints/roblox_incorrect_roact_usage.rs +++ b/selene-lib/src/lints/roblox_incorrect_roact_usage.rs @@ -94,6 +94,7 @@ impl Lint for IncorrectRoactUsageLint { ), Label::new(invalid_event.range), None, + Applicability::Unspecified, )); } @@ -114,6 +115,7 @@ impl Lint for IncorrectRoactUsageLint { )], Vec::new(), None, + Applicability::Unspecified, )); } _ => { @@ -125,6 +127,7 @@ impl Lint for IncorrectRoactUsageLint { ), Label::new(invalid_property.range), None, + Applicability::Unspecified, )); } } @@ -136,6 +139,7 @@ impl Lint for IncorrectRoactUsageLint { format!("`{}` is not a valid class", unknown_class.name), Label::new(unknown_class.range), None, + Applicability::Unspecified, )); } diff --git a/selene-lib/src/lints/roblox_suspicious_udim2_new.rs b/selene-lib/src/lints/roblox_suspicious_udim2_new.rs index 3ed4d865..59f801c1 100644 --- a/selene-lib/src/lints/roblox_suspicious_udim2_new.rs +++ b/selene-lib/src/lints/roblox_suspicious_udim2_new.rs @@ -35,9 +35,16 @@ fn create_diagnostic(mismatch: &MismatchedArgCount) -> Diagnostic { .to_owned()], Vec::new(), None, + Applicability::Unspecified, ) } else { - Diagnostic::new(code, message, primary_label, None) + Diagnostic::new( + code, + message, + primary_label, + None, + Applicability::Unspecified, + ) } } diff --git a/selene-lib/src/lints/shadowing.rs b/selene-lib/src/lints/shadowing.rs index 06fd7add..f9fe160a 100644 --- a/selene-lib/src/lints/shadowing.rs +++ b/selene-lib/src/lints/shadowing.rs @@ -70,6 +70,7 @@ impl Lint for ShadowingLint { "previously defined here".to_owned(), )], None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/standard_library.rs b/selene-lib/src/lints/standard_library.rs index 539b42eb..c1b3e405 100644 --- a/selene-lib/src/lints/standard_library.rs +++ b/selene-lib/src/lints/standard_library.rs @@ -253,6 +253,7 @@ impl StandardLibraryVisitor<'_> { ), Vec::new(), None, + Applicability::Unspecified, )); } } @@ -312,6 +313,7 @@ impl Visitor for StandardLibraryVisitor<'_> { Vec::new(), Vec::new(), None, + Applicability::Unspecified, )); } @@ -350,6 +352,7 @@ impl Visitor for StandardLibraryVisitor<'_> { Vec::new(), Vec::new(), None, + Applicability::Unspecified, )); } } @@ -431,6 +434,7 @@ impl Visitor for StandardLibraryVisitor<'_> { ), Label::from_node(call, None), None, + Applicability::Unspecified, )); return; @@ -477,6 +481,7 @@ impl Visitor for StandardLibraryVisitor<'_> { )], Vec::new(), None, + Applicability::Unspecified, )); return; @@ -558,6 +563,7 @@ impl Visitor for StandardLibraryVisitor<'_> { message.iter().cloned().collect(), Vec::new(), None, + Applicability::Unspecified, )); } @@ -596,6 +602,7 @@ impl Visitor for StandardLibraryVisitor<'_> { required_param_message, Vec::new(), None, + Applicability::Unspecified, )); } @@ -631,6 +638,7 @@ impl Visitor for StandardLibraryVisitor<'_> { ), ), None, + Applicability::Unspecified, )); } } diff --git a/selene-lib/src/lints/suspicious_reverse_loop.rs b/selene-lib/src/lints/suspicious_reverse_loop.rs index dddaf990..4b070970 100644 --- a/selene-lib/src/lints/suspicious_reverse_loop.rs +++ b/selene-lib/src/lints/suspicious_reverse_loop.rs @@ -38,6 +38,7 @@ impl Lint for SuspiciousReverseLoopLint { vec!["help: try adding `, -1` after `1`".to_owned()], Vec::new(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/type_check_inside_call.rs b/selene-lib/src/lints/type_check_inside_call.rs index f9b5cba3..f06fad59 100644 --- a/selene-lib/src/lints/type_check_inside_call.rs +++ b/selene-lib/src/lints/type_check_inside_call.rs @@ -39,6 +39,7 @@ impl Lint for TypeCheckInsideCallLint { vec!["note: this will always return `boolean`".to_owned()], Vec::new(), None, + Applicability::Unspecified, ) }) .collect() diff --git a/selene-lib/src/lints/unbalanced_assignments.rs b/selene-lib/src/lints/unbalanced_assignments.rs index 15d42389..68d31f3d 100644 --- a/selene-lib/src/lints/unbalanced_assignments.rs +++ b/selene-lib/src/lints/unbalanced_assignments.rs @@ -38,6 +38,7 @@ impl Lint for UnbalancedAssignmentsLint { "too many values on the right side of the assignment".to_owned(), Label::new(assignment.range), None, + Applicability::Unspecified, ) } else { let secondary_labels = match assignment.first_call { @@ -58,6 +59,7 @@ impl Lint for UnbalancedAssignmentsLint { Vec::new(), secondary_labels, None, + Applicability::Unspecified, ) } }) diff --git a/selene-lib/src/lints/undefined_variable.rs b/selene-lib/src/lints/undefined_variable.rs index ec42467f..225465f7 100644 --- a/selene-lib/src/lints/undefined_variable.rs +++ b/selene-lib/src/lints/undefined_variable.rs @@ -49,6 +49,7 @@ impl Lint for UndefinedVariableLint { ), Vec::new(), None, + Applicability::Unspecified, )); } } diff --git a/selene-lib/src/lints/unscoped_variables.rs b/selene-lib/src/lints/unscoped_variables.rs index 5c8f4bae..ccf83125 100644 --- a/selene-lib/src/lints/unscoped_variables.rs +++ b/selene-lib/src/lints/unscoped_variables.rs @@ -61,6 +61,7 @@ impl Lint for UnscopedVariablesLint { ), Label::new(reference.identifier), None, + Applicability::Unspecified, )); } } diff --git a/selene-lib/src/lints/unused_variable.rs b/selene-lib/src/lints/unused_variable.rs index 9e3b1f0a..067c48ff 100644 --- a/selene-lib/src/lints/unused_variable.rs +++ b/selene-lib/src/lints/unused_variable.rs @@ -206,6 +206,7 @@ impl Lint for UnusedVariableLint { }) .collect(), fixed_code, + Applicability::MachineApplicable, )); }; } diff --git a/selene/src/main.rs b/selene/src/main.rs index d64eeda8..ea5828f6 100644 --- a/selene/src/main.rs +++ b/selene/src/main.rs @@ -17,7 +17,7 @@ use codespan_reporting::{ term::DisplayStyle as CodespanDisplayStyle, }; use selene_lib::{ - lints::{Diagnostic, Severity}, + lints::{Applicability, Diagnostic, Severity}, *, }; use structopt::{clap, StructOpt}; @@ -349,21 +349,27 @@ fn read<R: Read>( if is_fix { let num_fixes = diagnostics .iter() - .filter(|diagnostic| diagnostic.diagnostic.fixed_code.is_some()) + .filter(|diagnostic| { + diagnostic.diagnostic.fixed_code.is_some() + && diagnostic.diagnostic.applicability == Applicability::MachineApplicable + }) .count(); // To handle potential conflicts with different lint suggestions, we apply conflicting fixes one at a time. // Then we re-evaluate the lints with the new code until there are no more fixes to apply - while diagnostics - .iter() - .any(|diagnostic| diagnostic.diagnostic.fixed_code.is_some()) - { + while diagnostics.iter().any(|diagnostic| { + diagnostic.diagnostic.fixed_code.is_some() + && diagnostic.diagnostic.applicability == Applicability::MachineApplicable + }) { fixed_code = apply_diagnostics_fixes( fixed_code.as_str(), &diagnostics .iter() .map(|diagnostic| &diagnostic.diagnostic) - .filter(|diagnostic| diagnostic.fixed_code.is_some()) + .filter(|diagnostic| { + diagnostic.fixed_code.is_some() + && diagnostic.applicability == Applicability::MachineApplicable + }) .collect::<Vec<_>>(), );