Skip to content

Commit a718c23

Browse files
committed
feat(linter): add vue/valid-define-props rule (#13565)
related #11440 https://eslint.vuejs.org/rules/valid-define-props.html `defineEmits` and `defineProps` behave the same, refactored code to some utils, so both rules reuses the logic.
1 parent bb2bcf0 commit a718c23

File tree

7 files changed

+575
-102
lines changed

7 files changed

+575
-102
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,3 +2440,7 @@ impl RuleRunner for crate::rules::vitest::require_local_test_context_for_concurr
24402440
impl RuleRunner for crate::rules::vue::valid_define_emits::ValidDefineEmits {
24412441
const NODE_TYPES: Option<&AstTypesBitset> = None;
24422442
}
2443+
2444+
impl RuleRunner for crate::rules::vue::valid_define_props::ValidDefineProps {
2445+
const NODE_TYPES: Option<&AstTypesBitset> = None;
2446+
}

crates/oxc_linter/src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ pub(crate) mod node {
625625

626626
pub(crate) mod vue {
627627
pub mod valid_define_emits;
628+
pub mod valid_define_props;
628629
}
629630

630631
oxc_macros::declare_all_lint_rules! {
@@ -1203,4 +1204,5 @@ oxc_macros::declare_all_lint_rules! {
12031204
vitest::prefer_to_be_truthy,
12041205
vitest::require_local_test_context_for_concurrent_snapshots,
12051206
vue::valid_define_emits,
1207+
vue::valid_define_props,
12061208
}

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

Lines changed: 22 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
use oxc_ast::{
2-
AstKind,
3-
ast::{
4-
CallExpression, ExportDefaultDeclarationKind, Expression, IdentifierReference,
5-
ObjectPropertyKind,
6-
},
7-
};
1+
use oxc_ast::AstKind;
82
use oxc_diagnostics::OxcDiagnostic;
93
use oxc_macros::declare_oxc_lint;
104
use oxc_span::Span;
115

12-
use crate::{ContextSubHost, context::LintContext, frameworks::FrameworkOptions, rule::Rule};
6+
use crate::{
7+
context::LintContext,
8+
frameworks::FrameworkOptions,
9+
rule::Rule,
10+
utils::{DefineMacroProblem, check_define_macro_call_expression, has_default_exports_property},
11+
};
1312

1413
fn has_type_and_arguments_diagnostic(span: Span) -> OxcDiagnostic {
1514
OxcDiagnostic::warn("`defineEmits` has both a type-only emit and an argument.")
@@ -140,7 +139,7 @@ impl Rule for ValidDefineEmits {
140139
fn run_once(&self, ctx: &LintContext) {
141140
let mut found: Option<Span> = None;
142141

143-
let has_other_script_emits = has_default_emits_exports(&ctx.other_file_hosts());
142+
let has_other_script_emits = has_default_exports_property(&ctx.other_file_hosts(), "emits");
144143
for node in ctx.nodes() {
145144
let AstKind::CallExpression(call_expr) = node.kind() else {
146145
continue;
@@ -161,105 +160,27 @@ impl Rule for ValidDefineEmits {
161160
}
162161
found = Some(call_expr.span);
163162

164-
handle_call_expression(call_expr, ctx, has_other_script_emits);
165-
}
166-
}
167-
168-
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
169-
ctx.frameworks_options() == FrameworkOptions::VueSetup
170-
}
171-
}
172-
173-
fn handle_call_expression(
174-
call_expr: &CallExpression,
175-
ctx: &LintContext,
176-
has_other_script_emits: bool,
177-
) {
178-
let has_type_args = call_expr.type_arguments.is_some();
179-
180-
if has_type_args && has_other_script_emits {
181-
ctx.diagnostic(define_in_both(call_expr.span));
182-
return;
183-
}
184-
185-
// `defineEmits` has type arguments and js arguments. Vue Compiler allows only one of them.
186-
if has_type_args && !call_expr.arguments.is_empty() {
187-
ctx.diagnostic(has_type_and_arguments_diagnostic(call_expr.span));
188-
return; // Skip if there are type arguments
189-
}
190-
191-
if has_type_args {
192-
// If there are type arguments, we don't need to check the arguments.
193-
return;
194-
}
195-
196-
let Some(expression) = call_expr.arguments.first().and_then(|first| first.as_expression())
197-
else {
198-
// `defineEmits();` is valid when `export default { emits: [] }` is defined
199-
if !has_other_script_emits {
200-
ctx.diagnostic(events_not_defined(call_expr.span));
201-
}
202-
return;
203-
};
204-
205-
if has_other_script_emits {
206-
ctx.diagnostic(define_in_both(call_expr.span));
207-
return;
208-
}
209-
210-
match expression {
211-
Expression::ArrayExpression(_) | Expression::ObjectExpression(_) => {}
212-
Expression::Identifier(identifier) => {
213-
if !is_non_local_reference(identifier, ctx) {
214-
ctx.diagnostic(referencing_locally(call_expr.span));
215-
}
216-
}
217-
_ => {
218-
ctx.diagnostic(referencing_locally(call_expr.span));
219-
}
220-
}
221-
}
222-
223-
pub fn is_non_local_reference(identifier: &IdentifierReference, ctx: &LintContext<'_>) -> bool {
224-
if let Some(symbol_id) = ctx.semantic().scoping().get_root_binding(&identifier.name) {
225-
return matches!(
226-
ctx.semantic().symbol_declaration(symbol_id).kind(),
227-
AstKind::ImportSpecifier(_)
228-
);
229-
}
230-
231-
// variables outside the current `<script>` block are valid.
232-
// This is the same for unresolved variables.
233-
true
234-
}
235-
236-
fn has_default_emits_exports(others: &Vec<&ContextSubHost<'_>>) -> bool {
237-
for host in others {
238-
for other_node in host.semantic().nodes() {
239-
let AstKind::ExportDefaultDeclaration(export) = other_node.kind() else {
240-
continue;
241-
};
242-
243-
let ExportDefaultDeclarationKind::ObjectExpression(export_obj) = &export.declaration
163+
let Some(problem) =
164+
check_define_macro_call_expression(call_expr, ctx, has_other_script_emits)
244165
else {
245166
continue;
246167
};
247168

248-
let has_emits_exports = export_obj.properties.iter().any(|property| {
249-
let ObjectPropertyKind::ObjectProperty(property) = property else {
250-
return false;
251-
};
252-
253-
property.key.name().is_some_and(|name| name == "emits")
254-
});
255-
256-
if has_emits_exports {
257-
return true;
258-
}
169+
let diagnostic = match problem {
170+
DefineMacroProblem::DefineInBoth => define_in_both(call_expr.span),
171+
DefineMacroProblem::HasTypeAndArguments => {
172+
has_type_and_arguments_diagnostic(call_expr.span)
173+
}
174+
DefineMacroProblem::EventsNotDefined => events_not_defined(call_expr.span),
175+
DefineMacroProblem::ReferencingLocally => referencing_locally(call_expr.span),
176+
};
177+
ctx.diagnostic(diagnostic);
259178
}
260179
}
261180

262-
false
181+
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
182+
ctx.frameworks_options() == FrameworkOptions::VueSetup
183+
}
263184
}
264185

265186
#[test]

0 commit comments

Comments
 (0)