From fb6a9a66825c9c4c1d7df100084366e6dbde032f Mon Sep 17 00:00:00 2001 From: yefan Date: Wed, 31 Dec 2025 15:42:26 +0800 Subject: [PATCH 1/3] implement --- .../src/generated/rule_runner_impls.rs | 5 + crates/oxc_linter/src/rules.rs | 2 + .../vue/no_this_in_before_route_enter.rs | 242 ++++++++++++++++++ .../vue_no_this_in_before_route_enter.snap | 38 +++ 4 files changed, 287 insertions(+) create mode 100644 crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs create mode 100644 crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 362a0e1820abc..3cbd444786942 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4090,6 +4090,11 @@ impl RuleRunner for crate::rules::vue::no_required_prop_with_default::NoRequired const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::vue::no_this_in_before_route_enter::NoThisInBeforeRouteEnter { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::vue::prefer_import_from_vue::PreferImportFromVue { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 0210cbd00fc30..ebce9a787c3ee 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -693,6 +693,7 @@ pub(crate) mod vue { pub mod no_import_compiler_macros; pub mod no_multiple_slot_args; pub mod no_required_prop_with_default; + pub mod no_this_in_before_route_enter; pub mod prefer_import_from_vue; pub mod require_default_export; pub mod require_typed_ref; @@ -1344,6 +1345,7 @@ oxc_macros::declare_all_lint_rules! { vue::no_import_compiler_macros, vue::no_multiple_slot_args, vue::no_required_prop_with_default, + vue::no_this_in_before_route_enter, vue::prefer_import_from_vue, vue::require_default_export, vue::require_typed_ref, diff --git a/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs new file mode 100644 index 0000000000000..fd7cd9baa834e --- /dev/null +++ b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs @@ -0,0 +1,242 @@ +use oxc_ast::{ + AstKind, + ast::{ExportDefaultDeclarationKind, Expression, Function, ObjectPropertyKind, ThisExpression}, +}; +use oxc_ast_visit::Visit; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::ScopeFlags; +use oxc_span::Span; + +use crate::{AstNode, context::LintContext, rule::Rule}; + +fn no_this_in_before_route_enter_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("'beforeRouteEnter' does NOT have access to `this` component instance.") + .with_help("Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoThisInBeforeRouteEnter; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow this usage in a beforeRouteEnter method. + /// + /// ### Why is this bad? + /// + /// Because lack of this in the beforeRouteEnter. + /// This behavior isn't obvious, so it's pretty easy to make a TypeError. Especially during some refactor. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// export default { + /// beforeRouteEnter(to, from, next) { + /// this.a; // Error: 'this' is not available + /// next(); + /// } + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// export default { + /// beforeRouteEnter(to, from, next) { + /// // anything without this + /// } + /// } + /// ``` + NoThisInBeforeRouteEnter, + vue, + correctness, +); + +impl Rule for NoThisInBeforeRouteEnter { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::ExportDefaultDeclaration(export_default_decl) = node.kind() else { + return; + }; + let ExportDefaultDeclarationKind::ObjectExpression(obj_expr) = + &export_default_decl.declaration + else { + return; + }; + + for prop in &obj_expr.properties { + if let ObjectPropertyKind::ObjectProperty(obj_prop) = prop { + let Some(key_name) = obj_prop.key.static_name() else { + continue; + }; + if key_name != "beforeRouteEnter" { + continue; + } + let function_body = match &obj_prop.value { + Expression::FunctionExpression(func_expr) => func_expr.body.as_ref(), + _ => continue, + }; + + let Some(function_body) = function_body else { + continue; + }; + + let mut finder = ThisFinder::new(); + finder.visit_function_body(function_body); + for span in finder.found_this_expressions { + ctx.diagnostic(no_this_in_before_route_enter_diagnostic(span)); + } + } + } + } + + fn should_run(&self, ctx: &crate::context::ContextHost) -> bool { + ctx.file_extension().is_some_and(|ext| ext == "vue") + } +} + +struct ThisFinder { + found_this_expressions: Vec, +} + +impl ThisFinder { + fn new() -> Self { + Self { found_this_expressions: Vec::new() } + } +} + +impl<'a> Visit<'a> for ThisFinder { + fn visit_this_expression(&mut self, expr: &ThisExpression) { + self.found_this_expressions.push(expr.span); + } + + fn visit_function(&mut self, _func: &Function<'a>, _flags: ScopeFlags) {} +} + +#[test] +fn test() { + use crate::tester::Tester; + use std::path::PathBuf; + + let pass = vec![ + ( + r#" + + + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + export default { + beforeRouteEnter(to, from, next) { + this.a; + } + }; + ", + None, + None, + Some(PathBuf::from("test.js")), + ), + ]; + + let fail = vec![ + ( + r" + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ]; + + Tester::new(NoThisInBeforeRouteEnter::NAME, NoThisInBeforeRouteEnter::PLUGIN, pass, fail) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap new file mode 100644 index 0000000000000..09baee2f5ac79 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap @@ -0,0 +1,38 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ╭─[no_this_in_before_route_enter.tsx:5:8] + 4 │ beforeRouteEnter(to, from, next) { + 5 │ this.a; + · ──── + 6 │ next(); + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ╭─[no_this_in_before_route_enter.tsx:5:8] + 4 │ beforeRouteEnter: function(to, from, next) { + 5 │ this.b; + · ──── + 6 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ╭─[no_this_in_before_route_enter.tsx:5:8] + 4 │ beforeRouteEnter: function(to, from, next) { + 5 │ this.atrr = this.method(); + · ──── + 6 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ╭─[no_this_in_before_route_enter.tsx:5:20] + 4 │ beforeRouteEnter: function(to, from, next) { + 5 │ this.atrr = this.method(); + · ──── + 6 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. From af3f40e7271b0b3e77f6aa0a0f27ae3c11d71c16 Mon Sep 17 00:00:00 2001 From: yefan Date: Wed, 31 Dec 2025 15:53:42 +0800 Subject: [PATCH 2/3] update snap --- crates/oxc_linter/src/generated/rule_runner_impls.rs | 3 ++- .../src/rules/vue/no_this_in_before_route_enter.rs | 2 +- .../src/snapshots/vue_no_this_in_before_route_enter.snap | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 3cbd444786942..eb664d94b4c0a 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4091,7 +4091,8 @@ impl RuleRunner for crate::rules::vue::no_required_prop_with_default::NoRequired } impl RuleRunner for crate::rules::vue::no_this_in_before_route_enter::NoThisInBeforeRouteEnter { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ExportDefaultDeclaration])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs index fd7cd9baa834e..b35bed85cf8a7 100644 --- a/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs +++ b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs @@ -11,7 +11,7 @@ use oxc_span::Span; use crate::{AstNode, context::LintContext, rule::Rule}; fn no_this_in_before_route_enter_diagnostic(span: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("'beforeRouteEnter' does NOT have access to `this` component instance.") + OxcDiagnostic::warn("`beforeRouteEnter` does NOT have access to `this` component instance.") .with_help("Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`.") .with_label(span) } diff --git a/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap index 09baee2f5ac79..fc2a3ce33154d 100644 --- a/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap +++ b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap @@ -1,7 +1,7 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. ╭─[no_this_in_before_route_enter.tsx:5:8] 4 │ beforeRouteEnter(to, from, next) { 5 │ this.a; @@ -10,7 +10,7 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. - ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. ╭─[no_this_in_before_route_enter.tsx:5:8] 4 │ beforeRouteEnter: function(to, from, next) { 5 │ this.b; @@ -19,7 +19,7 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. - ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. ╭─[no_this_in_before_route_enter.tsx:5:8] 4 │ beforeRouteEnter: function(to, from, next) { 5 │ this.atrr = this.method(); @@ -28,7 +28,7 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. - ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` navigation guard should not access `this`. + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. ╭─[no_this_in_before_route_enter.tsx:5:20] 4 │ beforeRouteEnter: function(to, from, next) { 5 │ this.atrr = this.method(); From 7b5522abe27c21aae5a4823d1d7a7cfe752be1ec Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 31 Dec 2025 22:22:34 +0000 Subject: [PATCH 3/3] u --- .../vue/no_this_in_before_route_enter.rs | 287 ++++++++++++------ .../vue_no_this_in_before_route_enter.snap | 84 +++-- 2 files changed, 257 insertions(+), 114 deletions(-) diff --git a/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs index b35bed85cf8a7..d8777d1cceef3 100644 --- a/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs +++ b/crates/oxc_linter/src/rules/vue/no_this_in_before_route_enter.rs @@ -27,7 +27,7 @@ declare_oxc_lint!( /// ### Why is this bad? /// /// Because lack of this in the beforeRouteEnter. - /// This behavior isn't obvious, so it's pretty easy to make a TypeError. Especially during some refactor. + /// This behavior isn't obvious, so it's pretty easy to make a TypeError. Especially while refactoring. /// /// ### Examples /// @@ -56,37 +56,38 @@ declare_oxc_lint!( impl Rule for NoThisInBeforeRouteEnter { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let AstKind::ExportDefaultDeclaration(export_default_decl) = node.kind() else { - return; - }; + let AstKind::ExportDefaultDeclaration(export_default_decl) = node.kind() else { return }; let ExportDefaultDeclarationKind::ObjectExpression(obj_expr) = &export_default_decl.declaration else { return; }; - for prop in &obj_expr.properties { - if let ObjectPropertyKind::ObjectProperty(obj_prop) = prop { - let Some(key_name) = obj_prop.key.static_name() else { - continue; - }; - if key_name != "beforeRouteEnter" { - continue; - } - let function_body = match &obj_prop.value { - Expression::FunctionExpression(func_expr) => func_expr.body.as_ref(), - _ => continue, - }; + let before_route_enter_prop = obj_expr.properties.iter().find_map(|prop| { + if let ObjectPropertyKind::ObjectProperty(obj_prop) = prop + && let Some(key_name) = obj_prop.key.static_name() + && key_name == "beforeRouteEnter" + { + Some(obj_prop) + } else { + None + } + }); - let Some(function_body) = function_body else { - continue; - }; + if let Some(before_route_enter_prop) = before_route_enter_prop { + let function_body = match &before_route_enter_prop.value { + Expression::FunctionExpression(func_expr) => func_expr.body.as_ref(), + _ => return, + }; - let mut finder = ThisFinder::new(); - finder.visit_function_body(function_body); - for span in finder.found_this_expressions { - ctx.diagnostic(no_this_in_before_route_enter_diagnostic(span)); - } + let Some(function_body) = function_body else { + return; + }; + + let mut finder = ThisFinder::new(); + finder.visit_function_body(function_body); + for span in finder.found_this_expressions { + ctx.diagnostic(no_this_in_before_route_enter_diagnostic(span)); } } } @@ -122,72 +123,109 @@ fn test() { let pass = vec![ ( r#" - - - "#, None, None, Some(PathBuf::from("test.vue")), ), ( - r" - ", + r#" + "#, None, None, Some(PathBuf::from("test.vue")), ), ( - r" - ", + r#" + "#, + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r#" + "#, None, None, Some(PathBuf::from("test.vue")), ), ( r" - ", + } + }; + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r" + ", None, None, Some(PathBuf::from("test.vue")), ), ( r" - export default { - beforeRouteEnter(to, from, next) { + export default { + beforeRouteEnter(to, from, next) { this.a; - } - }; - ", + } + }; + ", None, None, Some(PathBuf::from("test.js")), @@ -196,41 +234,110 @@ fn test() { let fail = vec![ ( - r" - ", + r#" + "#, None, None, Some(PathBuf::from("test.vue")), ), ( - r" - ", + r#" + "#, None, None, Some(PathBuf::from("test.vue")), ), ( - r" - ", + r#" + "#, + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r#" + "#, + None, + None, + Some(PathBuf::from("test.vue")), + ), + // this inside if condition + ( + r#" + "#, + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r#" + "#, None, None, Some(PathBuf::from("test.vue")), diff --git a/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap index fc2a3ce33154d..ec0ae6a42b12d 100644 --- a/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap +++ b/crates/oxc_linter/src/snapshots/vue_no_this_in_before_route_enter.snap @@ -2,37 +2,73 @@ source: crates/oxc_linter/src/tester.rs --- ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. - ╭─[no_this_in_before_route_enter.tsx:5:8] - 4 │ beforeRouteEnter(to, from, next) { - 5 │ this.a; - · ──── - 6 │ next(); - ╰──── + ╭─[no_this_in_before_route_enter.tsx:10:17] + 9 │ beforeRouteEnter() { + 10 │ this.xxx(); + · ──── + 11 │ } + ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. - ╭─[no_this_in_before_route_enter.tsx:5:8] - 4 │ beforeRouteEnter: function(to, from, next) { - 5 │ this.b; - · ──── - 6 │ } - ╰──── + ╭─[no_this_in_before_route_enter.tsx:10:17] + 9 │ beforeRouteEnter: function() { + 10 │ this.method(); + · ──── + 11 │ } + ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. - ╭─[no_this_in_before_route_enter.tsx:5:8] - 4 │ beforeRouteEnter: function(to, from, next) { - 5 │ this.atrr = this.method(); - · ──── - 6 │ } - ╰──── + ╭─[no_this_in_before_route_enter.tsx:10:17] + 9 │ beforeRouteEnter() { + 10 │ this.attr = this.method(); + · ──── + 11 │ } + ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. - ╭─[no_this_in_before_route_enter.tsx:5:20] - 4 │ beforeRouteEnter: function(to, from, next) { - 5 │ this.atrr = this.method(); - · ──── - 6 │ } - ╰──── + ╭─[no_this_in_before_route_enter.tsx:10:29] + 9 │ beforeRouteEnter() { + 10 │ this.attr = this.method(); + · ──── + 11 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. + ╭─[no_this_in_before_route_enter.tsx:10:17] + 9 │ beforeRouteEnter: function() { + 10 │ this.attr = this.method(); + · ──── + 11 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. + ╭─[no_this_in_before_route_enter.tsx:10:29] + 9 │ beforeRouteEnter: function() { + 10 │ this.attr = this.method(); + · ──── + 11 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. + ╭─[no_this_in_before_route_enter.tsx:10:21] + 9 │ beforeRouteEnter() { + 10 │ if (this.method()) {} + · ──── + 11 │ } + ╰──── + help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`. + + ⚠ eslint-plugin-vue(no-this-in-before-route-enter): `beforeRouteEnter` does NOT have access to `this` component instance. + ╭─[no_this_in_before_route_enter.tsx:10:29] + 9 │ beforeRouteEnter: function() { + 10 │ if (true) { this.method(); } + · ──── + 11 │ } + ╰──── help: Use the callback's `vm` parameter instead of `this` in `beforeRouteEnter`.