@@ -4,7 +4,11 @@ use cow_utils::CowUtils;
4
4
use oxc_allocator:: Box ;
5
5
use oxc_ast:: {
6
6
AstKind ,
7
- ast:: { Argument , Expression , ObjectExpression , ObjectPropertyKind , PropertyKey } ,
7
+ ast:: {
8
+ Argument , BindingPattern , Expression , FormalParameter , ObjectExpression ,
9
+ ObjectPropertyKind , PropertyKey , TSLiteral , TSLiteralType , TSType , TSTypeAnnotation ,
10
+ TemplateLiteral ,
11
+ } ,
8
12
} ;
9
13
use oxc_diagnostics:: OxcDiagnostic ;
10
14
use oxc_macros:: declare_oxc_lint;
@@ -83,6 +87,9 @@ impl Rule for NoInvalidFetchOptions {
83
87
}
84
88
}
85
89
90
+ // set to method_name to "UNKNOWN" if we can't infer the method name
91
+ const UNKNOWN_METHOD_NAME : Cow < ' static , str > = Cow :: Borrowed ( "UNKNOWN" ) ;
92
+
86
93
fn is_invalid_fetch_options < ' a > (
87
94
obj_expr : & ' a Box < ' _ , ObjectExpression < ' _ > > ,
88
95
ctx : & ' a LintContext < ' _ > ,
@@ -112,8 +119,13 @@ fn is_invalid_fetch_options<'a>(
112
119
body_span = key_ident. span ;
113
120
}
114
121
} else if key_ident_name == "method" {
115
- let method = match & obj_prop. value {
116
- Expression :: StringLiteral ( value_ident) => & value_ident. value ,
122
+ match & obj_prop. value {
123
+ Expression :: StringLiteral ( value_ident) => {
124
+ method_name = value_ident. value . cow_to_ascii_uppercase ( ) ;
125
+ }
126
+ Expression :: TemplateLiteral ( template_lit) => {
127
+ method_name = extract_method_name_from_template_literal ( template_lit) ;
128
+ }
117
129
Expression :: Identifier ( value_ident) => {
118
130
let symbols = ctx. semantic ( ) . symbols ( ) ;
119
131
let reference_id = value_ident. reference_id ( ) ;
@@ -124,20 +136,57 @@ fn is_invalid_fetch_options<'a>(
124
136
125
137
let decl = ctx. semantic ( ) . nodes ( ) . get_node ( symbols. get_declaration ( symbol_id) ) ;
126
138
127
- let AstKind :: VariableDeclarator ( declarator) = decl. kind ( ) else {
128
- continue ;
129
- } ;
130
-
131
- let Some ( Expression :: StringLiteral ( str_lit) ) = & declarator. init else {
132
- continue ;
133
- } ;
134
-
135
- & str_lit. value
139
+ match decl. kind ( ) {
140
+ AstKind :: VariableDeclarator ( declarator) => match & declarator. init {
141
+ Some ( Expression :: StringLiteral ( str_lit) ) => {
142
+ method_name = str_lit. value . cow_to_ascii_uppercase ( ) ;
143
+ }
144
+ Some ( Expression :: TemplateLiteral ( template_lit) ) => {
145
+ method_name =
146
+ extract_method_name_from_template_literal ( template_lit) ;
147
+ }
148
+ _ => {
149
+ method_name = UNKNOWN_METHOD_NAME ;
150
+ continue ;
151
+ }
152
+ } ,
153
+ AstKind :: FormalParameter ( FormalParameter {
154
+ pattern : BindingPattern { type_annotation : Some ( annotation) , .. } ,
155
+ ..
156
+ } ) => {
157
+ let TSTypeAnnotation { type_annotation, .. } = & * * annotation;
158
+ match type_annotation {
159
+ TSType :: TSUnionType ( union_type) => {
160
+ if !union_type. types . iter ( ) . any ( |ty| {
161
+ if let TSType :: TSLiteralType ( ty) = ty {
162
+ let TSLiteralType { literal, .. } = & * * ty;
163
+ if let TSLiteral :: StringLiteral ( str_lit) = literal {
164
+ return str_lit. value . to_ascii_uppercase ( ) == "GET"
165
+ || str_lit. value . to_ascii_uppercase ( )
166
+ == "HEAD" ;
167
+ }
168
+ }
169
+ false
170
+ } ) {
171
+ method_name = UNKNOWN_METHOD_NAME ;
172
+ }
173
+ }
174
+ TSType :: TSLiteralType ( literal_type) => {
175
+ let TSLiteralType { literal, .. } = & * * literal_type;
176
+ if let TSLiteral :: StringLiteral ( str_lit) = literal {
177
+ method_name = str_lit. value . cow_to_ascii_uppercase ( ) ;
178
+ }
179
+ }
180
+ _ => {
181
+ method_name = UNKNOWN_METHOD_NAME ;
182
+ }
183
+ }
184
+ }
185
+ _ => continue ,
186
+ }
136
187
}
137
188
_ => continue ,
138
189
} ;
139
-
140
- method_name = method. cow_to_ascii_uppercase ( ) ;
141
190
}
142
191
}
143
192
@@ -148,6 +197,18 @@ fn is_invalid_fetch_options<'a>(
148
197
}
149
198
}
150
199
200
+ fn extract_method_name_from_template_literal < ' a > (
201
+ template_lit : & ' a TemplateLiteral < ' a > ,
202
+ ) -> Cow < ' a , str > {
203
+ if let Some ( template_element_value) = template_lit. quasis . get ( 0 ) {
204
+ // only one template element
205
+ if template_element_value. tail {
206
+ return template_element_value. value . raw . cow_to_ascii_uppercase ( ) ;
207
+ }
208
+ }
209
+ UNKNOWN_METHOD_NAME
210
+ }
211
+
151
212
#[ test]
152
213
fn test ( ) {
153
214
use crate :: tester:: Tester ;
@@ -182,6 +243,14 @@ fn test() {
182
243
r#"fetch('/', {body: new URLSearchParams({ data: "test" }), method: "POST"})"# ,
183
244
r#"const method = "post"; new Request(url, {method, body: "foo=bar"})"# ,
184
245
r#"const method = "post"; fetch(url, {method, body: "foo=bar"})"# ,
246
+ r#"const method = `post`; fetch(url, {method, body: "foo=bar"})"# ,
247
+ r#"const method = `po${"st"}`; fetch(url, {method, body: "foo=bar"})"# ,
248
+ r#"function foo(method: "POST" | "PUT", body: string) {
249
+ return new Request(url, {method, body});
250
+ }"# ,
251
+ r#"function foo(method: string, body: string) {
252
+ return new Request(url, {method, body});
253
+ }"# ,
185
254
] ;
186
255
187
256
let fail = vec ! [
@@ -192,16 +261,21 @@ fn test() {
192
261
r#"fetch(url, {method: "HEAD", body})"# ,
193
262
r#"new Request(url, {method: "HEAD", body})"# ,
194
263
r#"fetch(url, {method: "head", body})"# ,
264
+ r#"fetch(url, {method: `head`, body: "foo=bar"})"# ,
195
265
r#"new Request(url, {method: "head", body})"# ,
196
266
r#"const method = "head"; new Request(url, {method, body: "foo=bar"})"# ,
197
267
r#"const method = "head"; fetch(url, {method, body: "foo=bar"})"# ,
268
+ r#"const method = `head`; fetch(url, {method, body: "foo=bar"})"# ,
198
269
r"fetch(url, {body}, extraArgument)" ,
199
270
r"new Request(url, {body}, extraArgument)" ,
200
271
r#"fetch(url, {body: undefined, body: "foo=bar"});"# ,
201
272
r#"new Request(url, {body: undefined, body: "foo=bar"});"# ,
202
273
r#"fetch(url, {method: "post", body: "foo=bar", method: "HEAD"});"# ,
203
274
r#"new Request(url, {method: "post", body: "foo=bar", method: "HEAD"});"# ,
204
275
r#"fetch('/', {body: new URLSearchParams({ data: "test" })})"# ,
276
+ r#"function foo(method: "HEAD" | "GET") {
277
+ return new Request(url, {method, body: ""});
278
+ }"# ,
205
279
] ;
206
280
207
281
Tester :: new ( NoInvalidFetchOptions :: NAME , NoInvalidFetchOptions :: PLUGIN , pass, fail)
0 commit comments