style(linter): introduce the writing style from PR #5491 and reduce the if nesting (#5512)

Related to #5491
This commit is contained in:
dalaoshu 2024-09-06 11:19:48 +08:00 committed by GitHub
parent c3cfbfb480
commit 2a43fa4efd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 515 additions and 524 deletions

View file

@ -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()
}

View file

@ -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));
}
}
}

View file

@ -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)
},
);
}
}
}

View file

@ -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,
};

View file

@ -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));
}
}
}

View file

@ -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,
)));
}
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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);
}
}
}
}
_ => {}
}
_ => {}
}
}
}

View file

@ -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);
}
}
}
}
_ => {}
}
_ => {}
}
}
}

View file

@ -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));
}
}
}
_ => {}
}
}
}

View file

@ -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(

View file

@ -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)
});
}
}
}

View file

@ -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),
);
}
}
}

View file

@ -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)
});
}
}
}

View file

@ -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,
));
}
}
}

View file

@ -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));
}
}
}

View file

@ -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
}

View file

@ -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));
}
}
_ => {}
}
}
}

View file

@ -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)))
}

View file

@ -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 {

View file

@ -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),
));
}
}

View file

@ -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,
}
})
})

View file

@ -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"),
);
}
}
}

View file

@ -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]

View file

@ -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) {

View file

@ -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.");

View file

@ -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 {