mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
Related to #5491
This commit is contained in:
parent
c3cfbfb480
commit
2a43fa4efd
28 changed files with 515 additions and 524 deletions
|
|
@ -430,11 +430,7 @@ pub fn get_function_like_declaration<'b>(
|
|||
ctx: &LintContext<'b>,
|
||||
) -> Option<&'b BindingIdentifier<'b>> {
|
||||
let parent = outermost_paren_parent(node, ctx)?;
|
||||
let decl = parent.kind().as_variable_declarator()?;
|
||||
|
||||
if let AstKind::VariableDeclarator(decl) = parent.kind() {
|
||||
let ident = decl.id.get_binding_identifier()?;
|
||||
Some(ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
decl.id.get_binding_identifier()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,25 +65,30 @@ impl Rule for DefaultCase {
|
|||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::SwitchStatement(switch) = node.kind() {
|
||||
let cases = &switch.cases;
|
||||
if !cases.is_empty() && !cases.iter().any(|case| case.test.is_none()) {
|
||||
if let Some(last_case) = cases.last() {
|
||||
let has_default_comment = ctx
|
||||
.semantic()
|
||||
.trivias()
|
||||
.comments_range(last_case.span.start..switch.span.end)
|
||||
.last()
|
||||
.is_some_and(|comment| {
|
||||
let raw = comment.span.source_text(ctx.semantic().source_text()).trim();
|
||||
match &self.comment_pattern {
|
||||
Some(comment_pattern) => comment_pattern.is_match(raw),
|
||||
None => raw.eq_ignore_ascii_case("no default"),
|
||||
}
|
||||
});
|
||||
|
||||
if !has_default_comment {
|
||||
ctx.diagnostic(default_case_diagnostic(switch.span));
|
||||
if cases.is_empty() || cases.iter().any(|case| case.test.is_none()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(last_case) = cases.last() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let has_default_comment = ctx
|
||||
.semantic()
|
||||
.trivias()
|
||||
.comments_range(last_case.span.start..switch.span.end)
|
||||
.last()
|
||||
.is_some_and(|comment| {
|
||||
let raw = comment.span.source_text(ctx.semantic().source_text()).trim();
|
||||
match &self.comment_pattern {
|
||||
Some(comment_pattern) => comment_pattern.is_match(raw),
|
||||
None => raw.eq_ignore_ascii_case("no default"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !has_default_comment {
|
||||
ctx.diagnostic(default_case_diagnostic(switch.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,68 +83,74 @@ declare_oxc_lint!(
|
|||
|
||||
impl Rule for ForDirection {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::ForStatement(for_loop) = node.kind() {
|
||||
if let Some(Expression::BinaryExpression(test)) = &for_loop.test {
|
||||
let (counter, counter_position) = match (&test.left, &test.right) {
|
||||
(Expression::Identifier(counter), _) => (counter, LEFT),
|
||||
(_, Expression::Identifier(counter)) => (counter, RIGHT),
|
||||
_ => return,
|
||||
};
|
||||
let test_operator = &test.operator;
|
||||
let wrong_direction = match (test_operator, counter_position) {
|
||||
(BinaryOperator::LessEqualThan | BinaryOperator::LessThan, RIGHT)
|
||||
| (BinaryOperator::GreaterEqualThan | BinaryOperator::GreaterThan, LEFT) => {
|
||||
FORWARD
|
||||
let AstKind::ForStatement(for_loop) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(Expression::BinaryExpression(test)) = &for_loop.test else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (counter, counter_position) = match (&test.left, &test.right) {
|
||||
(Expression::Identifier(counter), _) => (counter, LEFT),
|
||||
(_, Expression::Identifier(counter)) => (counter, RIGHT),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let test_operator = &test.operator;
|
||||
let wrong_direction = match (test_operator, counter_position) {
|
||||
(BinaryOperator::LessEqualThan | BinaryOperator::LessThan, RIGHT)
|
||||
| (BinaryOperator::GreaterEqualThan | BinaryOperator::GreaterThan, LEFT) => FORWARD,
|
||||
(BinaryOperator::LessEqualThan | BinaryOperator::LessThan, LEFT)
|
||||
| (BinaryOperator::GreaterEqualThan | BinaryOperator::GreaterThan, RIGHT) => BACKWARD,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let Some(update) = &for_loop.update else {
|
||||
return;
|
||||
};
|
||||
|
||||
let update_direction = get_update_direction(update, counter);
|
||||
if update_direction == wrong_direction {
|
||||
ctx.diagnostic_with_dangerous_fix(
|
||||
for_direction_diagnostic(test.span, get_update_span(update)),
|
||||
|fixer| {
|
||||
let mut span = Span::new(0, 0);
|
||||
|
||||
let mut new_operator_str = "";
|
||||
|
||||
match update {
|
||||
Expression::UpdateExpression(update) => {
|
||||
if update.span().start == update.argument.span().start {
|
||||
span.start = update.argument.span().end;
|
||||
span.end = update.span().end;
|
||||
} else {
|
||||
span.start = update.span().start;
|
||||
span.end = update.argument.span().start;
|
||||
}
|
||||
|
||||
if let UpdateOperator::Increment = update.operator {
|
||||
new_operator_str = "--";
|
||||
} else if let UpdateOperator::Decrement = update.operator {
|
||||
new_operator_str = "++";
|
||||
}
|
||||
}
|
||||
Expression::AssignmentExpression(update) => {
|
||||
span.start = update.left.span().end;
|
||||
span.end = update.right.span().start;
|
||||
|
||||
if let AssignmentOperator::Addition = update.operator {
|
||||
new_operator_str = "-=";
|
||||
} else if let AssignmentOperator::Subtraction = update.operator {
|
||||
new_operator_str = "+=";
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(BinaryOperator::LessEqualThan | BinaryOperator::LessThan, LEFT)
|
||||
| (BinaryOperator::GreaterEqualThan | BinaryOperator::GreaterThan, RIGHT) => {
|
||||
BACKWARD
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if let Some(update) = &for_loop.update {
|
||||
let update_direction = get_update_direction(update, counter);
|
||||
if update_direction == wrong_direction {
|
||||
let update_span = get_update_span(update);
|
||||
ctx.diagnostic_with_dangerous_fix(
|
||||
for_direction_diagnostic(test.span, update_span),
|
||||
|fixer| {
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
if let Expression::UpdateExpression(update) = update {
|
||||
if update.span().start == update.argument.span().start {
|
||||
start = update.argument.span().end;
|
||||
end = update.span().end;
|
||||
} else {
|
||||
start = update.span().start;
|
||||
end = update.argument.span().start;
|
||||
}
|
||||
} else if let Expression::AssignmentExpression(update) = update {
|
||||
start = update.left.span().end;
|
||||
end = update.right.span().start;
|
||||
}
|
||||
let span = Span::new(start, end);
|
||||
let mut new_operator_str = "";
|
||||
if let Expression::UpdateExpression(update) = update {
|
||||
if let UpdateOperator::Increment = update.operator {
|
||||
new_operator_str = "--";
|
||||
} else if let UpdateOperator::Decrement = update.operator {
|
||||
new_operator_str = "++";
|
||||
}
|
||||
} else if let Expression::AssignmentExpression(update) = update {
|
||||
if let AssignmentOperator::Addition = update.operator {
|
||||
new_operator_str = "-=";
|
||||
} else if let AssignmentOperator::Subtraction = update.operator
|
||||
{
|
||||
new_operator_str = "+=";
|
||||
}
|
||||
}
|
||||
fixer.replace(span, new_operator_str)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fixer.replace(span, new_operator_str)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ impl Rule for NoAsyncPromiseExecutor {
|
|||
let mut span = match expression.get_inner_expression() {
|
||||
Expression::ArrowFunctionExpression(arrow) if arrow.r#async => arrow.span,
|
||||
Expression::FunctionExpression(func) if func.r#async => func.span,
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,21 +68,22 @@ impl Rule for NoConsole {
|
|||
}
|
||||
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::CallExpression(call_expr) = node.kind() {
|
||||
if let Some(mem) = call_expr.callee.as_member_expression() {
|
||||
if let Expression::Identifier(ident) = mem.object() {
|
||||
if ctx.semantic().is_reference_to_global_variable(ident)
|
||||
&& ident.name == "console"
|
||||
&& !self
|
||||
.allow
|
||||
.iter()
|
||||
.any(|s| mem.static_property_name().is_some_and(|f| f == s))
|
||||
{
|
||||
if let Some(mem) = mem.static_property_info() {
|
||||
ctx.diagnostic(no_console_diagnostic(mem.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
let AstKind::CallExpression(call_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(mem) = call_expr.callee.as_member_expression() else {
|
||||
return;
|
||||
};
|
||||
let Expression::Identifier(ident) = mem.object() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if ctx.semantic().is_reference_to_global_variable(ident)
|
||||
&& ident.name == "console"
|
||||
&& !self.allow.iter().any(|s| mem.static_property_name().is_some_and(|f| f == s))
|
||||
{
|
||||
if let Some(mem) = mem.static_property_info() {
|
||||
ctx.diagnostic(no_console_diagnostic(mem.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,36 +34,38 @@ declare_oxc_lint!(
|
|||
|
||||
impl Rule for NoTemplateCurlyInString {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::StringLiteral(literal) = node.kind() {
|
||||
let text = literal.value.as_str();
|
||||
if let Some(start_index) = text.find("${") {
|
||||
let mut open_braces_count = 0;
|
||||
let mut end_index = None;
|
||||
let AstKind::StringLiteral(literal) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (i, c) in text[start_index..].char_indices() {
|
||||
let real_index = start_index + i;
|
||||
if c == '{' {
|
||||
open_braces_count += 1;
|
||||
} else if c == '}' && open_braces_count > 0 {
|
||||
open_braces_count -= 1;
|
||||
if open_braces_count == 0 {
|
||||
end_index = Some(real_index);
|
||||
break;
|
||||
}
|
||||
let text = literal.value.as_str();
|
||||
if let Some(start_index) = text.find("${") {
|
||||
let mut open_braces_count = 0;
|
||||
let mut end_index = None;
|
||||
|
||||
for (i, c) in text[start_index..].char_indices() {
|
||||
let real_index = start_index + i;
|
||||
if c == '{' {
|
||||
open_braces_count += 1;
|
||||
} else if c == '}' && open_braces_count > 0 {
|
||||
open_braces_count -= 1;
|
||||
if open_braces_count == 0 {
|
||||
end_index = Some(real_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(end_index) = end_index {
|
||||
let literal_span_start = literal.span.start + 1;
|
||||
let match_start = u32::try_from(start_index)
|
||||
.expect("Conversion from usize to u32 failed for match_start");
|
||||
let match_end = u32::try_from(end_index + 1)
|
||||
.expect("Conversion from usize to u32 failed for match_end");
|
||||
ctx.diagnostic(no_template_curly_in_string_diagnostic(Span::new(
|
||||
literal_span_start + match_start,
|
||||
literal_span_start + match_end,
|
||||
)));
|
||||
}
|
||||
if let Some(end_index) = end_index {
|
||||
let literal_span_start = literal.span.start + 1;
|
||||
let match_start = u32::try_from(start_index)
|
||||
.expect("Conversion from usize to u32 failed for match_start");
|
||||
let match_end = u32::try_from(end_index + 1)
|
||||
.expect("Conversion from usize to u32 failed for match_end");
|
||||
ctx.diagnostic(no_template_curly_in_string_diagnostic(Span::new(
|
||||
literal_span_start + match_start,
|
||||
literal_span_start + match_end,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,15 +135,15 @@ impl Rule for NoThisBeforeSuper {
|
|||
impl NoThisBeforeSuper {
|
||||
fn is_wanted_node(node: &AstNode, ctx: &LintContext<'_>) -> Option<bool> {
|
||||
let parent = ctx.nodes().parent_node(node.id())?;
|
||||
if let AstKind::MethodDefinition(mdef) = parent.kind() {
|
||||
if matches!(mdef.kind, MethodDefinitionKind::Constructor) {
|
||||
let parent_2 = ctx.nodes().parent_node(parent.id())?;
|
||||
let parent_3 = ctx.nodes().parent_node(parent_2.id())?;
|
||||
if let AstKind::Class(c) = parent_3.kind() {
|
||||
let super_class = c.super_class.as_ref()?;
|
||||
return Some(!matches!(super_class, Expression::NullLiteral(_)));
|
||||
}
|
||||
}
|
||||
let method_def = parent.kind().as_method_definition()?;
|
||||
|
||||
if matches!(method_def.kind, MethodDefinitionKind::Constructor) {
|
||||
let parent_2 = ctx.nodes().parent_node(parent.id())?;
|
||||
let parent_3 = ctx.nodes().parent_node(parent_2.id())?;
|
||||
|
||||
let class = parent_3.kind().as_class()?;
|
||||
let super_class = class.super_class.as_ref()?;
|
||||
return Some(!matches!(super_class, Expression::NullLiteral(_)));
|
||||
}
|
||||
|
||||
Some(false)
|
||||
|
|
|
|||
|
|
@ -176,11 +176,8 @@ impl NoUnusedVars {
|
|||
// find FormalParameters. Should be the next parent of param, but this
|
||||
// is safer.
|
||||
let Some((params, params_id)) = symbol.iter_parents().find_map(|p| {
|
||||
if let AstKind::FormalParameters(params) = p.kind() {
|
||||
Some((params, p.id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let params = p.kind().as_formal_parameters()?;
|
||||
Some((params, p.id()))
|
||||
}) else {
|
||||
debug_assert!(false, "FormalParameter should always have a parent FormalParameters");
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -58,41 +58,42 @@ declare_oxc_lint!(
|
|||
|
||||
impl Rule for PreferNumericLiterals {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::CallExpression(call_expr) = node.kind() {
|
||||
match &call_expr.callee.without_parentheses() {
|
||||
Expression::Identifier(ident) if ident.name == "parseInt" => {
|
||||
if is_parse_int_call(ctx, ident, None) {
|
||||
let AstKind::CallExpression(call_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match &call_expr.callee.without_parentheses() {
|
||||
Expression::Identifier(ident) if ident.name == "parseInt" => {
|
||||
if is_parse_int_call(ctx, ident, None) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
}
|
||||
Expression::StaticMemberExpression(member_expr) => {
|
||||
if let Expression::Identifier(ident) = &member_expr.object {
|
||||
if is_parse_int_call(ctx, ident, Some(member_expr)) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
} else if let Expression::ParenthesizedExpression(paren_expr) = &member_expr.object
|
||||
{
|
||||
if let Expression::Identifier(ident) = &paren_expr.expression {
|
||||
if is_parse_int_call(ctx, ident, Some(member_expr)) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::StaticMemberExpression(member_expr) => {
|
||||
}
|
||||
Expression::ChainExpression(chain_expr) => {
|
||||
if let Some(MemberExpression::StaticMemberExpression(member_expr)) =
|
||||
chain_expr.expression.as_member_expression()
|
||||
{
|
||||
if let Expression::Identifier(ident) = &member_expr.object {
|
||||
if is_parse_int_call(ctx, ident, Some(member_expr)) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
} else if let Expression::ParenthesizedExpression(paren_expr) =
|
||||
&member_expr.object
|
||||
{
|
||||
if let Expression::Identifier(ident) = &paren_expr.expression {
|
||||
if is_parse_int_call(ctx, ident, Some(member_expr)) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::ChainExpression(chain_expr) => {
|
||||
if let Some(MemberExpression::StaticMemberExpression(member_expr)) =
|
||||
chain_expr.expression.as_member_expression()
|
||||
{
|
||||
if let Expression::Identifier(ident) = &member_expr.object {
|
||||
if is_parse_int_call(ctx, ident, Some(member_expr)) {
|
||||
check_arguments(call_expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,40 +66,41 @@ impl Rule for Radix {
|
|||
}
|
||||
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::CallExpression(call_expr) = node.kind() {
|
||||
match call_expr.callee.without_parentheses() {
|
||||
Expression::Identifier(ident) => {
|
||||
if ident.name == "parseInt"
|
||||
let AstKind::CallExpression(call_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match call_expr.callee.without_parentheses() {
|
||||
Expression::Identifier(ident) => {
|
||||
if ident.name == "parseInt"
|
||||
&& ctx.symbols().is_global_reference(ident.reference_id().unwrap())
|
||||
{
|
||||
Self::check_arguments(self, call_expr, ctx);
|
||||
}
|
||||
}
|
||||
Expression::StaticMemberExpression(member_expr) => {
|
||||
if let Expression::Identifier(ident) = member_expr.object.without_parentheses() {
|
||||
if ident.name == "Number"
|
||||
&& member_expr.property.name == "parseInt"
|
||||
&& ctx.symbols().is_global_reference(ident.reference_id().unwrap())
|
||||
{
|
||||
Self::check_arguments(self, call_expr, ctx);
|
||||
}
|
||||
}
|
||||
Expression::StaticMemberExpression(member_expr) => {
|
||||
if let Expression::Identifier(ident) = member_expr.object.without_parentheses()
|
||||
{
|
||||
}
|
||||
Expression::ChainExpression(chain_expr) => {
|
||||
if let Some(member_expr) = chain_expr.expression.as_member_expression() {
|
||||
if let Expression::Identifier(ident) = member_expr.object() {
|
||||
if ident.name == "Number"
|
||||
&& member_expr.property.name == "parseInt"
|
||||
&& member_expr.static_property_name() == Some("parseInt")
|
||||
&& ctx.symbols().is_global_reference(ident.reference_id().unwrap())
|
||||
{
|
||||
Self::check_arguments(self, call_expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::ChainExpression(chain_expr) => {
|
||||
if let Some(member_expr) = chain_expr.expression.as_member_expression() {
|
||||
if let Expression::Identifier(ident) = member_expr.object() {
|
||||
if ident.name == "Number"
|
||||
&& member_expr.static_property_name() == Some("parseInt")
|
||||
&& ctx.symbols().is_global_reference(ident.reference_id().unwrap())
|
||||
{
|
||||
Self::check_arguments(self, call_expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,48 +73,48 @@ declare_oxc_lint!(
|
|||
|
||||
impl Rule for RequireAwait {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::FunctionBody(body) = node.kind() {
|
||||
if body.is_empty() {
|
||||
return;
|
||||
}
|
||||
let AstKind::FunctionBody(body) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
if body.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(parent) = ctx.nodes().parent_node(node.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(parent) = ctx.nodes().parent_node(node.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match parent.kind() {
|
||||
AstKind::Function(func) => {
|
||||
if func.r#async && !func.generator {
|
||||
let mut finder = AwaitFinder { found: false };
|
||||
finder.visit_function_body(body);
|
||||
if !finder.found {
|
||||
if let Some(AstKind::ObjectProperty(p)) =
|
||||
ctx.nodes().parent_kind(parent.id())
|
||||
{
|
||||
if let PropertyKey::StaticIdentifier(iden) = &p.key {
|
||||
ctx.diagnostic(require_await_diagnostic(iden.span));
|
||||
} else {
|
||||
ctx.diagnostic(require_await_diagnostic(func.span));
|
||||
}
|
||||
match parent.kind() {
|
||||
AstKind::Function(func) => {
|
||||
if func.r#async && !func.generator {
|
||||
let mut finder = AwaitFinder { found: false };
|
||||
finder.visit_function_body(body);
|
||||
if !finder.found {
|
||||
if let Some(AstKind::ObjectProperty(p)) =
|
||||
ctx.nodes().parent_kind(parent.id())
|
||||
{
|
||||
if let PropertyKey::StaticIdentifier(iden) = &p.key {
|
||||
ctx.diagnostic(require_await_diagnostic(iden.span));
|
||||
} else {
|
||||
ctx.diagnostic(require_await_diagnostic(
|
||||
func.id.as_ref().map_or(func.span, |ident| ident.span),
|
||||
));
|
||||
ctx.diagnostic(require_await_diagnostic(func.span));
|
||||
}
|
||||
} else {
|
||||
ctx.diagnostic(require_await_diagnostic(
|
||||
func.id.as_ref().map_or(func.span, |ident| ident.span),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
AstKind::ArrowFunctionExpression(func) => {
|
||||
if func.r#async {
|
||||
let mut finder = AwaitFinder { found: false };
|
||||
finder.visit_function_body(body);
|
||||
if !finder.found {
|
||||
ctx.diagnostic(require_await_diagnostic(func.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
AstKind::ArrowFunctionExpression(func) => {
|
||||
if func.r#async {
|
||||
let mut finder = AwaitFinder { found: false };
|
||||
finder.visit_function_body(body);
|
||||
if !finder.found {
|
||||
ctx.diagnostic(require_await_diagnostic(func.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,42 +214,32 @@ fn check_deep_namespace_for_node(
|
|||
namespaces: &[String],
|
||||
module: &Arc<ModuleRecord>,
|
||||
ctx: &LintContext<'_>,
|
||||
) {
|
||||
if let AstKind::MemberExpression(expr) = node.kind() {
|
||||
let Some((span, name)) = expr.static_property_info() else {
|
||||
return;
|
||||
};
|
||||
) -> Option<()> {
|
||||
let expr = node.kind().as_member_expression()?;
|
||||
let (span, name) = expr.static_property_info()?;
|
||||
|
||||
if let Some(module_source) = get_module_request_name(name, module) {
|
||||
let Some(parent_node) = ctx.nodes().parent_node(node.id()) else {
|
||||
return;
|
||||
};
|
||||
if let Some(module_record) = module.loaded_modules.get(module_source.as_str()) {
|
||||
let mut namespaces = namespaces.to_owned();
|
||||
namespaces.push(name.into());
|
||||
check_deep_namespace_for_node(
|
||||
parent_node,
|
||||
source,
|
||||
&namespaces,
|
||||
module_record.value(),
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
check_binding_exported(
|
||||
name,
|
||||
|| {
|
||||
if namespaces.len() > 1 {
|
||||
no_export_in_deeply_imported_namespace(span, name, &namespaces.join("."))
|
||||
} else {
|
||||
no_export(span, name, source)
|
||||
}
|
||||
},
|
||||
module,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
if let Some(module_source) = get_module_request_name(name, module) {
|
||||
let parent_node = ctx.nodes().parent_node(node.id())?;
|
||||
let module_record = module.loaded_modules.get(module_source.as_str())?;
|
||||
let mut namespaces = namespaces.to_owned();
|
||||
namespaces.push(name.into());
|
||||
check_deep_namespace_for_node(parent_node, source, &namespaces, module_record.value(), ctx);
|
||||
} else {
|
||||
check_binding_exported(
|
||||
name,
|
||||
|| {
|
||||
if namespaces.len() > 1 {
|
||||
no_export_in_deeply_imported_namespace(span, name, &namespaces.join("."))
|
||||
} else {
|
||||
no_export(span, name, source)
|
||||
}
|
||||
},
|
||||
module,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn check_deep_namespace_for_object_pattern(
|
||||
|
|
|
|||
|
|
@ -94,28 +94,31 @@ impl Rule for NoAutofocus {
|
|||
}
|
||||
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::JSXElement(jsx_el) = node.kind() {
|
||||
if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") {
|
||||
let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else {
|
||||
return;
|
||||
};
|
||||
if self.ignore_non_dom {
|
||||
if HTML_TAG.contains(&element_type) {
|
||||
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
|
||||
ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| {
|
||||
fixer.delete(&attr.span)
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let AstKind::JSXElement(jsx_el) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") else {
|
||||
return;
|
||||
};
|
||||
let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.ignore_non_dom {
|
||||
if HTML_TAG.contains(&element_type) {
|
||||
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
|
||||
ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| {
|
||||
fixer.delete(&attr.span)
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
|
||||
ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| {
|
||||
fixer.delete(&attr.span)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,26 +58,27 @@ static DEFAULT_ROLE_EXCEPTIONS: phf::Map<&'static str, &'static str> = phf_map!
|
|||
|
||||
impl Rule for NoRedundantRoles {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::JSXOpeningElement(jsx_el) = node.kind() {
|
||||
if let Some(component) = get_element_type(ctx, jsx_el) {
|
||||
if let Some(JSXAttributeItem::Attribute(attr)) =
|
||||
has_jsx_prop_ignore_case(jsx_el, "role")
|
||||
{
|
||||
if let Some(JSXAttributeValue::StringLiteral(role_values)) = &attr.value {
|
||||
let roles: Vec<String> = role_values
|
||||
.value
|
||||
.split_whitespace()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect();
|
||||
for role in &roles {
|
||||
let exceptions = DEFAULT_ROLE_EXCEPTIONS.get(&component);
|
||||
if exceptions.map_or(false, |set| set.contains(role)) {
|
||||
ctx.diagnostic_with_fix(
|
||||
no_redundant_roles_diagnostic(attr.span, &component, role),
|
||||
|fixer| fixer.delete_range(attr.span),
|
||||
);
|
||||
}
|
||||
}
|
||||
let AstKind::JSXOpeningElement(jsx_el) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(component) = get_element_type(ctx, jsx_el) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_ignore_case(jsx_el, "role") {
|
||||
if let Some(JSXAttributeValue::StringLiteral(role_values)) = &attr.value {
|
||||
let roles: Vec<String> = role_values
|
||||
.value
|
||||
.split_whitespace()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect();
|
||||
for role in &roles {
|
||||
let exceptions = DEFAULT_ROLE_EXCEPTIONS.get(&component);
|
||||
if exceptions.map_or(false, |set| set.contains(role)) {
|
||||
ctx.diagnostic_with_fix(
|
||||
no_redundant_roles_diagnostic(attr.span, &component, role),
|
||||
|fixer| fixer.delete_range(attr.span),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,30 +64,33 @@ fn is_implicit_diagnostic(span: Span, x1: &str, x2: &str, x3: &str) -> OxcDiagno
|
|||
|
||||
impl Rule for RoleSupportsAriaProps {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::JSXOpeningElement(jsx_el) = node.kind() {
|
||||
if let Some(el_type) = get_element_type(ctx, jsx_el) {
|
||||
let role = has_jsx_prop_ignore_case(jsx_el, "role");
|
||||
let role_value = role.map_or_else(
|
||||
|| get_implicit_role(jsx_el, &el_type),
|
||||
|i| get_string_literal_prop_value(i),
|
||||
);
|
||||
let is_implicit = role_value.is_some() && role.is_none();
|
||||
if let Some(role_value) = role_value {
|
||||
if !VALID_ARIA_ROLES.contains(role_value) {
|
||||
return;
|
||||
}
|
||||
let invalid_props = get_invalid_aria_props_for_role(role_value);
|
||||
for attr in &jsx_el.attributes {
|
||||
if let JSXAttributeItem::Attribute(attr) = attr {
|
||||
let name = get_jsx_attribute_name(&attr.name).to_lowercase();
|
||||
if invalid_props.contains(&&name.as_str()) {
|
||||
ctx.diagnostic(if is_implicit {
|
||||
is_implicit_diagnostic(attr.span, &name, role_value, &el_type)
|
||||
} else {
|
||||
default(attr.span, &name, role_value)
|
||||
});
|
||||
}
|
||||
}
|
||||
let AstKind::JSXOpeningElement(jsx_el) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(el_type) = get_element_type(ctx, jsx_el) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let role = has_jsx_prop_ignore_case(jsx_el, "role");
|
||||
let role_value = role.map_or_else(
|
||||
|| get_implicit_role(jsx_el, &el_type),
|
||||
|i| get_string_literal_prop_value(i),
|
||||
);
|
||||
let is_implicit = role_value.is_some() && role.is_none();
|
||||
if let Some(role_value) = role_value {
|
||||
if !VALID_ARIA_ROLES.contains(role_value) {
|
||||
return;
|
||||
}
|
||||
let invalid_props = get_invalid_aria_props_for_role(role_value);
|
||||
for attr in &jsx_el.attributes {
|
||||
if let JSXAttributeItem::Attribute(attr) = attr {
|
||||
let name = get_jsx_attribute_name(&attr.name).to_lowercase();
|
||||
if invalid_props.contains(&&name.as_str()) {
|
||||
ctx.diagnostic(if is_implicit {
|
||||
is_implicit_diagnostic(attr.span, &name, role_value, &el_type)
|
||||
} else {
|
||||
default(attr.span, &name, role_value)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,62 +107,67 @@ const NEXT_POLYFILLED_FEATURES: Set<&'static str> = phf_set! {
|
|||
|
||||
impl Rule for NoUnwantedPolyfillio {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::JSXOpeningElement(jsx_el) = node.kind() {
|
||||
let Some(tag_name) = jsx_el.name.get_identifier_name() else { return };
|
||||
let AstKind::JSXOpeningElement(jsx_el) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if tag_name.as_str() != "script" {
|
||||
let next_script_import_local_name = get_next_script_import_local_name(ctx);
|
||||
if !matches!(next_script_import_local_name, Some(import) if tag_name.as_str() == import.as_str())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
let Some(tag_name) = jsx_el.name.get_identifier_name() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if jsx_el.attributes.len() == 0 {
|
||||
if tag_name.as_str() != "script" {
|
||||
let next_script_import_local_name = get_next_script_import_local_name(ctx);
|
||||
if !matches!(next_script_import_local_name, Some(import) if tag_name.as_str() == import.as_str())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(JSXAttributeItem::Attribute(src)) = jsx_el.attributes.iter().find(|attr| {
|
||||
matches!(
|
||||
attr,
|
||||
JSXAttributeItem::Attribute(jsx_attr)
|
||||
if matches!(
|
||||
&jsx_attr.name,
|
||||
JSXAttributeName::Identifier(id) if id.name.as_str() == "src"
|
||||
)
|
||||
)
|
||||
}) else {
|
||||
if jsx_el.attributes.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(JSXAttributeItem::Attribute(src)) = jsx_el.attributes.iter().find(|attr| {
|
||||
matches!(
|
||||
attr,
|
||||
JSXAttributeItem::Attribute(jsx_attr)
|
||||
if matches!(
|
||||
&jsx_attr.name,
|
||||
JSXAttributeName::Identifier(id) if id.name.as_str() == "src"
|
||||
)
|
||||
)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(JSXAttributeValue::StringLiteral(src_value)) = &src.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
if src_value.value.as_str().starts_with("https://cdn.polyfill.io/v2/")
|
||||
|| src_value.value.as_str().starts_with("https://polyfill.io/v3/")
|
||||
{
|
||||
let Ok(url) = url::Url::parse(src_value.value.as_str()) else {
|
||||
return;
|
||||
};
|
||||
let Some((_, features_value)) = url.query_pairs().find(|(key, _)| key == "features")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(JSXAttributeValue::StringLiteral(src_value)) = &src.value {
|
||||
if src_value.value.as_str().starts_with("https://cdn.polyfill.io/v2/")
|
||||
|| src_value.value.as_str().starts_with("https://polyfill.io/v3/")
|
||||
{
|
||||
let Ok(url) = url::Url::parse(src_value.value.as_str()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((_, features_value)) =
|
||||
url.query_pairs().find(|(key, _)| key == "features")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let unwanted_features: Vec<&str> = features_value
|
||||
.split(',')
|
||||
.filter(|feature| NEXT_POLYFILLED_FEATURES.contains(feature))
|
||||
.collect();
|
||||
if !unwanted_features.is_empty() {
|
||||
ctx.diagnostic(no_unwanted_polyfillio_diagnostic(
|
||||
&format!(
|
||||
"{} {}",
|
||||
unwanted_features.join(", "),
|
||||
if unwanted_features.len() > 1 { "are" } else { "is" }
|
||||
),
|
||||
src.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
let unwanted_features: Vec<&str> = features_value
|
||||
.split(',')
|
||||
.filter(|feature| NEXT_POLYFILLED_FEATURES.contains(feature))
|
||||
.collect();
|
||||
if !unwanted_features.is_empty() {
|
||||
ctx.diagnostic(no_unwanted_polyfillio_diagnostic(
|
||||
&format!(
|
||||
"{} {}",
|
||||
unwanted_features.join(", "),
|
||||
if unwanted_features.len() > 1 { "are" } else { "is" }
|
||||
),
|
||||
src.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,31 +39,32 @@ impl Rule for BadMinMaxFunc {
|
|||
let AstKind::CallExpression(call_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some((out_min_max, inner_exprs)) = Self::min_max(call_expr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some((out_min_max, inner_exprs)) = Self::min_max(call_expr) {
|
||||
for expr in inner_exprs {
|
||||
if let Some((inner_min_max, ..)) = Self::min_max(expr) {
|
||||
let constant_result = match (&out_min_max, &inner_min_max) {
|
||||
(MinMax::Max(max), MinMax::Min(min)) => {
|
||||
if max > min {
|
||||
Some(max)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
for expr in inner_exprs {
|
||||
if let Some((inner_min_max, ..)) = Self::min_max(expr) {
|
||||
let constant_result = match (&out_min_max, &inner_min_max) {
|
||||
(MinMax::Max(max), MinMax::Min(min)) => {
|
||||
if max > min {
|
||||
Some(max)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
(MinMax::Min(min), MinMax::Max(max)) => {
|
||||
if min < max {
|
||||
Some(min)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(constant) = constant_result {
|
||||
ctx.diagnostic(bad_min_max_func_diagnostic(*constant, call_expr.span));
|
||||
}
|
||||
(MinMax::Min(min), MinMax::Max(max)) => {
|
||||
if min < max {
|
||||
Some(min)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(constant) = constant_result {
|
||||
ctx.diagnostic(bad_min_max_func_diagnostic(*constant, call_expr.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,10 @@ fn resolve_flags<'a>(
|
|||
}
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if let Some(decl) = get_declaration_of_variable(ident, ctx) {
|
||||
if let AstKind::VariableDeclarator(var_decl) = decl.kind() {
|
||||
if let Some(init) = &var_decl.init {
|
||||
return resolve_flags(init, ctx);
|
||||
}
|
||||
}
|
||||
let decl = get_declaration_of_variable(ident, ctx)?;
|
||||
let var_decl = decl.kind().as_variable_declarator()?;
|
||||
if let Some(init) = &var_decl.init {
|
||||
return resolve_flags(init, ctx);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,34 +43,29 @@ impl Rule for NumberArgOutOfRange {
|
|||
let AstKind::CallExpression(expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(member) = expr.callee.get_member_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(member) = expr.callee.get_member_expr() {
|
||||
if let Some(Argument::NumericLiteral(literal)) = expr.arguments.first() {
|
||||
let value = literal.value;
|
||||
match member.static_property_name() {
|
||||
Some(name @ "toString") => {
|
||||
if !(2.0_f64..=36.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(
|
||||
name, 2, 36, expr.span,
|
||||
));
|
||||
}
|
||||
if let Some(Argument::NumericLiteral(literal)) = expr.arguments.first() {
|
||||
let value = literal.value;
|
||||
match member.static_property_name() {
|
||||
Some(name @ "toString") => {
|
||||
if !(2.0_f64..=36.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(name, 2, 36, expr.span));
|
||||
}
|
||||
Some(name @ ("toFixed" | "toExponential")) => {
|
||||
if !(0.0_f64..=20.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(
|
||||
name, 0, 20, expr.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(name @ "toPrecision") => {
|
||||
if !(1.0_f64..=21.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(
|
||||
name, 1, 21, expr.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(name @ ("toFixed" | "toExponential")) => {
|
||||
if !(0.0_f64..=20.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(name, 0, 20, expr.span));
|
||||
}
|
||||
}
|
||||
Some(name @ "toPrecision") => {
|
||||
if !(1.0_f64..=21.0_f64).contains(&value) {
|
||||
ctx.diagnostic(number_arg_out_of_range_diagnostic(name, 1, 21, expr.span));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ impl ReactPerfRule for JsxNoJsxAsProp {
|
|||
kind: &AstKind<'_>,
|
||||
_symbol_id: SymbolId,
|
||||
) -> Option<(/* decl */ Span, /* init */ Option<Span>)> {
|
||||
let AstKind::VariableDeclarator(decl) = kind else {
|
||||
return None;
|
||||
};
|
||||
let decl = kind.as_variable_declarator()?;
|
||||
let init_span = decl.init.as_ref().and_then(check_expression)?;
|
||||
Some((decl.id.span(), Some(init_span)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -547,9 +547,7 @@ fn get_type_only_named_import<'a>(
|
|||
source: &str,
|
||||
) -> Option<&'a ImportDeclaration<'a>> {
|
||||
let root = ctx.nodes().root_node()?;
|
||||
let AstKind::Program(program) = root.kind() else {
|
||||
return None;
|
||||
};
|
||||
let program = root.kind().as_program()?;
|
||||
|
||||
for stmt in &program.body {
|
||||
let Statement::ImportDeclaration(import_decl) = stmt else {
|
||||
|
|
|
|||
|
|
@ -39,53 +39,55 @@ declare_oxc_lint!(
|
|||
|
||||
impl Rule for NoNonNullAssertedOptionalChain {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::TSNonNullExpression(non_null_expr) = node.kind() {
|
||||
let chain_span = match non_null_expr.expression.get_inner_expression() {
|
||||
Expression::ChainExpression(chain) => match &chain.expression {
|
||||
ChainElement::ComputedMemberExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::StaticMemberExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::CallExpression(call) if call.optional => Some(call.callee.span()),
|
||||
_ => None,
|
||||
},
|
||||
Expression::CallExpression(call) => {
|
||||
if call.optional && !is_parent_member_or_call(node, ctx) {
|
||||
Some(call.callee.span())
|
||||
} else if let Some(member) = call.callee.as_member_expression() {
|
||||
if member.optional() && !is_parent_member_or_call(node, ctx) {
|
||||
Some(member.object().span())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
expr @ match_member_expression!(Expression) => {
|
||||
let member_expr = expr.to_member_expression();
|
||||
if member_expr.optional() && !is_parent_member_or_call(node, ctx) {
|
||||
Some(member_expr.object().span())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let AstKind::TSNonNullExpression(non_null_expr) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(chain_span) = chain_span {
|
||||
let chain_span_end = chain_span.end;
|
||||
let non_null_end = non_null_expr.span.end - 1;
|
||||
ctx.diagnostic(no_non_null_asserted_optional_chain_diagnostic(
|
||||
Span::new(chain_span_end, chain_span_end),
|
||||
Span::new(non_null_end, non_null_end),
|
||||
));
|
||||
let chain_span = match non_null_expr.expression.get_inner_expression() {
|
||||
Expression::ChainExpression(chain) => match &chain.expression {
|
||||
ChainElement::ComputedMemberExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::StaticMemberExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) if member.optional => {
|
||||
Some(member.object.span())
|
||||
}
|
||||
ChainElement::CallExpression(call) if call.optional => Some(call.callee.span()),
|
||||
_ => None,
|
||||
},
|
||||
Expression::CallExpression(call) => {
|
||||
if call.optional && !is_parent_member_or_call(node, ctx) {
|
||||
Some(call.callee.span())
|
||||
} else if let Some(member) = call.callee.as_member_expression() {
|
||||
if member.optional() && !is_parent_member_or_call(node, ctx) {
|
||||
Some(member.object().span())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
expr @ match_member_expression!(Expression) => {
|
||||
let member_expr = expr.to_member_expression();
|
||||
if member_expr.optional() && !is_parent_member_or_call(node, ctx) {
|
||||
Some(member_expr.object().span())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(chain_span) = chain_span {
|
||||
let chain_span_end = chain_span.end;
|
||||
let non_null_end = non_null_expr.span.end - 1;
|
||||
ctx.diagnostic(no_non_null_asserted_optional_chain_diagnostic(
|
||||
Span::new(chain_span_end, chain_span_end),
|
||||
Span::new(non_null_end, non_null_end),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use oxc_ast::{
|
|||
ast::{
|
||||
match_expression, Argument, ArrayExpressionElement, AssignmentExpression, AssignmentTarget,
|
||||
BindingPatternKind, CallExpression, Declaration, Expression, ModuleDeclaration,
|
||||
ObjectPropertyKind, PropertyKey, VariableDeclarator,
|
||||
ObjectPropertyKind, PropertyKey,
|
||||
},
|
||||
AstKind,
|
||||
};
|
||||
|
|
@ -239,18 +239,17 @@ fn check_expression(expr: &Expression, ctx: &LintContext<'_>) -> Option<oxc_span
|
|||
ident.reference_id.get().and_then(|ref_id| {
|
||||
tab.get_reference(ref_id).symbol_id().and_then(|symbol_id| {
|
||||
let decl = ctx.semantic().nodes().get_node(tab.get_declaration(symbol_id));
|
||||
if let AstKind::VariableDeclarator(VariableDeclarator {
|
||||
init: Some(Expression::StringLiteral(ref lit)),
|
||||
..
|
||||
}) = decl.kind()
|
||||
{
|
||||
if lit.value == "then" {
|
||||
Some(lit.span)
|
||||
} else {
|
||||
None
|
||||
let var_decl = decl.kind().as_variable_declarator()?;
|
||||
|
||||
match var_decl.init {
|
||||
Some(Expression::StringLiteral(ref lit)) => {
|
||||
if lit.value == "then" {
|
||||
Some(lit.span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -47,19 +47,15 @@ impl Rule for PreferStringSlice {
|
|||
return;
|
||||
};
|
||||
|
||||
let (span, name) = match member_expr {
|
||||
MemberExpression::StaticMemberExpression(v) => {
|
||||
if !matches!(v.property.name.as_str(), "substr" | "substring") {
|
||||
return;
|
||||
}
|
||||
(v.property.span, &v.property.name)
|
||||
if let MemberExpression::StaticMemberExpression(v) = member_expr {
|
||||
if !matches!(v.property.name.as_str(), "substr" | "substring") {
|
||||
return;
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
ctx.diagnostic_with_fix(prefer_string_slice_diagnostic(span, name.as_str()), |fixer| {
|
||||
fixer.replace(span, "slice")
|
||||
});
|
||||
ctx.diagnostic_with_fix(
|
||||
prefer_string_slice_diagnostic(v.property.span, v.property.name.as_str()),
|
||||
|fixer| fixer.replace(v.property.span, "slice"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,30 +66,29 @@ impl Rule for NoConditionalTests {
|
|||
}
|
||||
}
|
||||
|
||||
fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) {
|
||||
fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) -> Option<()> {
|
||||
let node = possible_jest_node.node;
|
||||
if let AstKind::CallExpression(call_expr) = node.kind() {
|
||||
if is_type_of_jest_fn_call(
|
||||
call_expr,
|
||||
possible_jest_node,
|
||||
ctx,
|
||||
&[
|
||||
JestFnKind::General(JestGeneralFnKind::Describe),
|
||||
JestFnKind::General(JestGeneralFnKind::Test),
|
||||
],
|
||||
) {
|
||||
let if_statement_node = ctx
|
||||
.nodes()
|
||||
.iter_parents(node.id())
|
||||
.find(|node| matches!(node.kind(), AstKind::IfStatement(_)));
|
||||
let call_expr = node.kind().as_call_expression()?;
|
||||
|
||||
let Some(node) = if_statement_node else { return };
|
||||
if is_type_of_jest_fn_call(
|
||||
call_expr,
|
||||
possible_jest_node,
|
||||
ctx,
|
||||
&[
|
||||
JestFnKind::General(JestGeneralFnKind::Describe),
|
||||
JestFnKind::General(JestGeneralFnKind::Test),
|
||||
],
|
||||
) {
|
||||
let if_statement_node = ctx
|
||||
.nodes()
|
||||
.iter_parents(node.id())
|
||||
.find(|node| matches!(node.kind(), AstKind::IfStatement(_)))?;
|
||||
|
||||
if let AstKind::IfStatement(if_statement) = node.kind() {
|
||||
ctx.diagnostic(no_conditional_tests(if_statement.span));
|
||||
}
|
||||
}
|
||||
let if_statement = if_statement_node.kind().as_if_statement()?;
|
||||
ctx.diagnostic(no_conditional_tests(if_statement.span));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ use phf::{phf_set, set::Set};
|
|||
pub fn as_endpoint_registration<'a, 'n>(
|
||||
node: &'n AstKind<'a>,
|
||||
) -> Option<(Option<Atom<'a>>, &'n [Argument<'a>])> {
|
||||
let AstKind::CallExpression(call) = node else {
|
||||
return None;
|
||||
};
|
||||
let call = node.as_call_expression()?;
|
||||
let callee = call.callee.as_member_expression()?;
|
||||
let method_name = callee.static_property_name()?;
|
||||
if !ROUTER_HANDLER_METHOD_NAMES.contains(method_name) {
|
||||
|
|
|
|||
|
|
@ -188,11 +188,8 @@ fn test_enums() {
|
|||
.nodes()
|
||||
.iter()
|
||||
.find_map(|node| {
|
||||
if let AstKind::TSEnumDeclaration(e) = node.kind() {
|
||||
Some((node, e))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let e = node.kind().as_ts_enum_declaration()?;
|
||||
Some((node, e))
|
||||
})
|
||||
.expect("Expected TS test case to have an enum declaration for A.");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use oxc_ast::AstKind;
|
||||
use oxc_semantic::Semantic;
|
||||
use oxc_syntax::class::ClassId;
|
||||
|
||||
|
|
@ -14,11 +13,12 @@ impl<'a> ClassTester<'a> {
|
|||
pub(super) fn has_class(semantic: Semantic<'a>, name: &str) -> Self {
|
||||
let class_id = semantic.classes().iter_enumerated().find_map(|(class_id, ast_node_id)| {
|
||||
let kind = semantic.nodes().kind(*ast_node_id);
|
||||
if let AstKind::Class(class) = kind {
|
||||
if class.id.clone().is_some_and(|id| id.name == name) {
|
||||
return Some(class_id);
|
||||
};
|
||||
}
|
||||
let class = kind.as_class()?;
|
||||
|
||||
if class.id.clone().is_some_and(|id| id.name == name) {
|
||||
return Some(class_id);
|
||||
};
|
||||
|
||||
None
|
||||
});
|
||||
ClassTester {
|
||||
|
|
|
|||
Loading…
Reference in a new issue