Skip to content

Commit e7ea688

Browse files
huangtiandi1999camc314
authored andcommitted
update
1 parent 90dfa2b commit e7ea688

File tree

2 files changed

+123
-65
lines changed

2 files changed

+123
-65
lines changed

crates/oxc_linter/src/rules/vue/no_required_prop_with_default.rs

Lines changed: 112 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -73,70 +73,104 @@ declare_oxc_lint!(
7373

7474
impl Rule for NoRequiredPropWithDefault {
7575
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
76-
match node.kind() {
77-
AstKind::CallExpression(call_expr) => {
78-
let Expression::Identifier(ident) = &call_expr.callee else {
76+
let is_vue = ctx.file_path().extension().is_some_and(|ext| ext == "vue");
77+
if is_vue {
78+
self.run_on_vue(node, ctx);
79+
} else {
80+
self.check_define_component(node, ctx);
81+
}
82+
}
83+
}
84+
85+
impl NoRequiredPropWithDefault {
86+
fn run_on_vue<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
87+
if ctx.frameworks_options() == FrameworkOptions::VueSetup {
88+
self.run_on_setup(node, ctx);
89+
} else {
90+
self.run_on_composition(node, ctx);
91+
}
92+
}
93+
94+
#[expect(clippy::unused_self)]
95+
fn check_define_component(&self, node: &AstNode<'_>, ctx: &LintContext<'_>) {
96+
// only check `defineComponent` method
97+
// e.g. `let component = defineComponent({ props: { name: { required: true, default: 'a' } } })`
98+
let AstKind::CallExpression(call_expr) = node.kind() else {
99+
return;
100+
};
101+
let Some(ident) = call_expr.callee.get_identifier_reference() else {
102+
return;
103+
};
104+
if ident.name.as_str() == "defineComponent" && call_expr.arguments.len() == 1 {
105+
let arg = &call_expr.arguments[0];
106+
let Some(Expression::ObjectExpression(obj)) = arg.as_expression() else {
107+
return;
108+
};
109+
handle_object_expression(ctx, obj);
110+
}
111+
}
112+
113+
fn run_on_setup<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
114+
let AstKind::CallExpression(call_expr) = node.kind() else {
115+
return;
116+
};
117+
let Some(ident) = call_expr.callee.get_identifier_reference() else {
118+
return;
119+
};
120+
121+
match ident.name.as_str() {
122+
"defineProps" => {
123+
if let Some(arge) = call_expr.arguments.first() {
124+
let Some(Expression::ObjectExpression(obj)) = arge.as_expression() else {
125+
return;
126+
};
127+
// Here we need to consider the following two examples
128+
// 1. const props = defineProps({ name: { required: true, default: 'a' } })
129+
// 2. const { name = 'a' } = defineProps({ name: { required: true } })
130+
let key_hash =
131+
collect_hash_from_variable_declarator(ctx, node).unwrap_or_default();
132+
handle_prop_object(ctx, obj, Some(&key_hash));
133+
}
134+
if call_expr.arguments.is_empty() {
135+
// if `defineProps` is used without arguments, we need to check the type arguments
136+
// e.g. `const { name = 'a' } = defineProps<IProp>()`
137+
let Some(type_args) = &call_expr.type_arguments else {
138+
return;
139+
};
140+
let Some(first_type_argument) = type_args.params.first() else {
141+
return;
142+
};
143+
if let Some(key_hash) = collect_hash_from_variable_declarator(ctx, node) {
144+
handle_type_argument(ctx, first_type_argument, &key_hash);
145+
}
146+
}
147+
}
148+
"withDefaults" if call_expr.arguments.len() == 2 => {
149+
let [first_arg, second_arg] = call_expr.arguments.as_slice() else {
79150
return;
80151
};
81-
let is_vue_setup = ctx.frameworks_options() == FrameworkOptions::VueSetup;
82-
match ident.name.as_str() {
83-
"defineProps" if is_vue_setup => {
84-
if let Some(arge) = call_expr.arguments.first() {
85-
let Some(Expression::ObjectExpression(obj)) = arge.as_expression()
86-
else {
87-
return;
88-
};
89-
// Here we need to consider the following two examples
90-
// 1. const props = defineProps({ name: { required: true, default: 'a' } })
91-
// 2. const { name = 'a' } = defineProps({ name: { required: true } })
92-
let key_hash = collect_hash_from_variable_declarator(ctx, node)
93-
.unwrap_or_default();
94-
handle_prop_object(ctx, obj, Some(&key_hash));
95-
}
96-
if call_expr.arguments.is_empty() {
97-
// if `defineProps` is used without arguments, we need to check the type arguments
98-
// e.g. `const { name = 'a' } = defineProps<IProp>()`
99-
let Some(type_args) = &call_expr.type_arguments else {
100-
return;
101-
};
102-
let Some(first_type_argument) = type_args.params.first() else {
103-
return;
104-
};
105-
if let Some(key_hash) = collect_hash_from_variable_declarator(ctx, node)
106-
{
107-
handle_type_argument(ctx, first_type_argument, &key_hash);
108-
}
109-
}
110-
}
111-
"defineComponent" if call_expr.arguments.len() == 1 => {
112-
let arg = &call_expr.arguments[0];
113-
let Some(Expression::ObjectExpression(obj)) = arg.as_expression() else {
114-
return;
115-
};
116-
handle_object_expression(ctx, obj);
117-
}
118-
"withDefaults" if call_expr.arguments.len() == 2 && is_vue_setup => {
119-
let [first_arg, second_arg] = call_expr.arguments.as_slice() else {
120-
return;
121-
};
122-
if let (Some(first_arg_expr), Some(second_arg_expr)) =
123-
(first_arg.as_expression(), second_arg.as_expression())
124-
{
125-
let Expression::ObjectExpression(second_obj_expr) =
126-
second_arg_expr.get_inner_expression()
127-
else {
128-
return;
129-
};
130-
let Some(key_hash) = collect_hash_from_object_expr(second_obj_expr)
131-
else {
132-
return;
133-
};
134-
process_define_props_call(ctx, first_arg_expr, &key_hash);
135-
}
136-
}
137-
_ => {}
152+
if let (Some(first_arg_expr), Some(second_arg_expr)) =
153+
(first_arg.as_expression(), second_arg.as_expression())
154+
{
155+
let Expression::ObjectExpression(second_obj_expr) =
156+
second_arg_expr.get_inner_expression()
157+
else {
158+
return;
159+
};
160+
let Some(key_hash) = collect_hash_from_object_expr(second_obj_expr) else {
161+
return;
162+
};
163+
process_define_props_call(ctx, first_arg_expr, &key_hash);
138164
}
139165
}
166+
_ => {
167+
self.check_define_component(node, ctx);
168+
}
169+
}
170+
}
171+
172+
fn run_on_composition<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
173+
match node.kind() {
140174
AstKind::ExportDefaultDeclaration(export_default_decl) => {
141175
let ExportDefaultDeclarationKind::ObjectExpression(obj_expr) =
142176
&export_default_decl.declaration
@@ -145,13 +179,11 @@ impl Rule for NoRequiredPropWithDefault {
145179
};
146180
handle_object_expression(ctx, obj_expr);
147181
}
148-
_ => {}
182+
_ => {
183+
self.check_define_component(node, ctx);
184+
}
149185
}
150186
}
151-
152-
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
153-
ctx.file_path().extension().is_some_and(|ext| ext == "vue")
154-
}
155187
}
156188

157189
fn collect_hash_from_object_expr(obj: &ObjectExpression) -> Option<FxHashSet<String>> {
@@ -577,6 +609,21 @@ fn test() {
577609
];
578610

579611
let fail = vec![
612+
(
613+
"
614+
const a = defineComponent({
615+
props: {
616+
'name': {
617+
required: true,
618+
default: 'Hello'
619+
}
620+
}
621+
})
622+
",
623+
None,
624+
None,
625+
Some(PathBuf::from("test.ts")),
626+
),
580627
(
581628
r#"
582629
<script setup lang="ts">

crates/oxc_linter/src/snapshots/vue_no_required_prop_with_default.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
---
22
source: crates/oxc_linter/src/tester.rs
33
---
4+
eslint-plugin-vue(no-required-prop-with-default): Prop "name" should be optional.
5+
╭─[no_required_prop_with_default.tsx:4:25]
6+
3props: {
7+
4 │ ╭─▶ 'name': {
8+
5 │ │ required: true,
9+
6 │ │ default: 'Hello'
10+
7 │ ╰─▶ }
11+
8 │ }
12+
╰────
13+
help: Remove the `required: true` option, or drop the `required` key entirely to make this prop optional.
14+
415
eslint-plugin-vue(no-required-prop-with-default): Prop "name" should be optional.
516
╭─[no_required_prop_with_default.tsx:4:19]
617
3interface TestPropType {

0 commit comments

Comments
 (0)