Skip to content

Commit

Permalink
feat(linter): improve no_invalid_fetch_options (#9347)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Feb 25, 2025
1 parent 7427900 commit 5ee2cab
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 14 deletions.
103 changes: 89 additions & 14 deletions crates/oxc_linter/src/rules/unicorn/no_invalid_fetch_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ use cow_utils::CowUtils;
use oxc_allocator::Box;
use oxc_ast::{
AstKind,
ast::{Argument, Expression, ObjectExpression, ObjectPropertyKind, PropertyKey},
ast::{
Argument, BindingPattern, Expression, FormalParameter, ObjectExpression,
ObjectPropertyKind, PropertyKey, TSLiteral, TSLiteralType, TSType, TSTypeAnnotation,
TemplateLiteral,
},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
Expand Down Expand Up @@ -83,6 +87,9 @@ impl Rule for NoInvalidFetchOptions {
}
}

// set to method_name to "UNKNOWN" if we can't infer the method name
const UNKNOWN_METHOD_NAME: Cow<'static, str> = Cow::Borrowed("UNKNOWN");

fn is_invalid_fetch_options<'a>(
obj_expr: &'a Box<'_, ObjectExpression<'_>>,
ctx: &'a LintContext<'_>,
Expand Down Expand Up @@ -112,8 +119,13 @@ fn is_invalid_fetch_options<'a>(
body_span = key_ident.span;
}
} else if key_ident_name == "method" {
let method = match &obj_prop.value {
Expression::StringLiteral(value_ident) => &value_ident.value,
match &obj_prop.value {
Expression::StringLiteral(value_ident) => {
method_name = value_ident.value.cow_to_ascii_uppercase();
}
Expression::TemplateLiteral(template_lit) => {
method_name = extract_method_name_from_template_literal(template_lit);
}
Expression::Identifier(value_ident) => {
let symbols = ctx.semantic().symbols();
let reference_id = value_ident.reference_id();
Expand All @@ -124,20 +136,58 @@ fn is_invalid_fetch_options<'a>(

let decl = ctx.semantic().nodes().get_node(symbols.get_declaration(symbol_id));

let AstKind::VariableDeclarator(declarator) = decl.kind() else {
continue;
};

let Some(Expression::StringLiteral(str_lit)) = &declarator.init else {
continue;
};

&str_lit.value
match decl.kind() {
AstKind::VariableDeclarator(declarator) => match &declarator.init {
Some(Expression::StringLiteral(str_lit)) => {
method_name = str_lit.value.cow_to_ascii_uppercase();
}
Some(Expression::TemplateLiteral(template_lit)) => {
method_name =
extract_method_name_from_template_literal(template_lit);
}
_ => {
method_name = UNKNOWN_METHOD_NAME;
continue;
}
},
AstKind::FormalParameter(FormalParameter {
pattern: BindingPattern { type_annotation: Some(annotation), .. },
..
}) => {
let TSTypeAnnotation { type_annotation, .. } = &**annotation;
match type_annotation {
TSType::TSUnionType(union_type) => {
if !union_type.types.iter().any(|ty| {
if let TSType::TSLiteralType(ty) = ty {
let TSLiteralType { literal, .. } = &**ty;
if let TSLiteral::StringLiteral(str_lit) = literal {
return str_lit.value.cow_to_ascii_uppercase()
== "GET"
|| str_lit.value.cow_to_ascii_uppercase()
== "HEAD";
}
}
false
}) {
method_name = UNKNOWN_METHOD_NAME;
}
}
TSType::TSLiteralType(literal_type) => {
let TSLiteralType { literal, .. } = &**literal_type;
if let TSLiteral::StringLiteral(str_lit) = literal {
method_name = str_lit.value.cow_to_ascii_uppercase();
}
}
_ => {
method_name = UNKNOWN_METHOD_NAME;
}
}
}
_ => continue,
}
}
_ => continue,
};

method_name = method.cow_to_ascii_uppercase();
}
}

Expand All @@ -148,6 +198,18 @@ fn is_invalid_fetch_options<'a>(
}
}

fn extract_method_name_from_template_literal<'a>(
template_lit: &'a TemplateLiteral<'a>,
) -> Cow<'a, str> {
if let Some(template_element_value) = template_lit.quasis.first() {
// only one template element
if template_element_value.tail {
return template_element_value.value.raw.cow_to_ascii_uppercase();
}
}
UNKNOWN_METHOD_NAME
}

#[test]
fn test() {
use crate::tester::Tester;
Expand Down Expand Up @@ -182,6 +244,14 @@ fn test() {
r#"fetch('/', {body: new URLSearchParams({ data: "test" }), method: "POST"})"#,
r#"const method = "post"; new Request(url, {method, body: "foo=bar"})"#,
r#"const method = "post"; fetch(url, {method, body: "foo=bar"})"#,
r#"const method = `post`; fetch(url, {method, body: "foo=bar"})"#,
r#"const method = `po${"st"}`; fetch(url, {method, body: "foo=bar"})"#,
r#"function foo(method: "POST" | "PUT", body: string) {
return new Request(url, {method, body});
}"#,
"function foo(method: string, body: string) {
return new Request(url, {method, body});
}",
];

let fail = vec![
Expand All @@ -192,16 +262,21 @@ fn test() {
r#"fetch(url, {method: "HEAD", body})"#,
r#"new Request(url, {method: "HEAD", body})"#,
r#"fetch(url, {method: "head", body})"#,
r#"fetch(url, {method: `head`, body: "foo=bar"})"#,
r#"new Request(url, {method: "head", body})"#,
r#"const method = "head"; new Request(url, {method, body: "foo=bar"})"#,
r#"const method = "head"; fetch(url, {method, body: "foo=bar"})"#,
r#"const method = `head`; fetch(url, {method, body: "foo=bar"})"#,
r"fetch(url, {body}, extraArgument)",
r"new Request(url, {body}, extraArgument)",
r#"fetch(url, {body: undefined, body: "foo=bar"});"#,
r#"new Request(url, {body: undefined, body: "foo=bar"});"#,
r#"fetch(url, {method: "post", body: "foo=bar", method: "HEAD"});"#,
r#"new Request(url, {method: "post", body: "foo=bar", method: "HEAD"});"#,
r#"fetch('/', {body: new URLSearchParams({ data: "test" })})"#,
r#"function foo(method: "HEAD" | "GET") {
return new Request(url, {method, body: ""});
}"#,
];

Tester::new(NoInvalidFetchOptions::NAME, NoInvalidFetchOptions::PLUGIN, pass, fail)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ source: crates/oxc_linter/src/tester.rs
· ────
╰────

eslint-plugin-unicorn(no-invalid-fetch-options): "body" is not allowed when method is "HEAD"
╭─[no_invalid_fetch_options.tsx:1:29]
1fetch(url, {method: `head`, body: "foo=bar"})
· ────
╰────

eslint-plugin-unicorn(no-invalid-fetch-options): "body" is not allowed when method is "HEAD"
╭─[no_invalid_fetch_options.tsx:1:35]
1new Request(url, {method: "head", body})
Expand All @@ -61,6 +67,12 @@ source: crates/oxc_linter/src/tester.rs
· ────
╰────

eslint-plugin-unicorn(no-invalid-fetch-options): "body" is not allowed when method is "HEAD"
╭─[no_invalid_fetch_options.tsx:1:44]
1const method = `head`; fetch(url, {method, body: "foo=bar"})
· ────
╰────

eslint-plugin-unicorn(no-invalid-fetch-options): "body" is not allowed when method is "GET"
╭─[no_invalid_fetch_options.tsx:1:13]
1fetch(url, {body}, extraArgument)
Expand Down Expand Up @@ -102,3 +114,11 @@ source: crates/oxc_linter/src/tester.rs
1fetch('/', {body: new URLSearchParams({ data: "test" })})
· ────
╰────

eslint-plugin-unicorn(no-invalid-fetch-options): "body" is not allowed when method is "GET"
╭─[no_invalid_fetch_options.tsx:2:46]
1function foo(method: "HEAD" | "GET") {
2return new Request(url, {method, body: ""});
· ────
3 │ }
╰────

0 comments on commit 5ee2cab

Please sign in to comment.