@@ -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,58 @@ 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 . cow_to_ascii_uppercase ( )
165
+ == "GET"
166
+ || str_lit. value . cow_to_ascii_uppercase ( )
167
+ == "HEAD" ;
168
+ }
169
+ }
170
+ false
171
+ } ) {
172
+ method_name = UNKNOWN_METHOD_NAME ;
173
+ }
174
+ }
175
+ TSType :: TSLiteralType ( literal_type) => {
176
+ let TSLiteralType { literal, .. } = & * * literal_type;
177
+ if let TSLiteral :: StringLiteral ( str_lit) = literal {
178
+ method_name = str_lit. value . cow_to_ascii_uppercase ( ) ;
179
+ }
180
+ }
181
+ _ => {
182
+ method_name = UNKNOWN_METHOD_NAME ;
183
+ }
184
+ }
185
+ }
186
+ _ => continue ,
187
+ }
136
188
}
137
189
_ => continue ,
138
190
} ;
139
-
140
- method_name = method. cow_to_ascii_uppercase ( ) ;
141
191
}
142
192
}
143
193
@@ -148,6 +198,18 @@ fn is_invalid_fetch_options<'a>(
148
198
}
149
199
}
150
200
201
+ fn extract_method_name_from_template_literal < ' a > (
202
+ template_lit : & ' a TemplateLiteral < ' a > ,
203
+ ) -> Cow < ' a , str > {
204
+ if let Some ( template_element_value) = template_lit. quasis . first ( ) {
205
+ // only one template element
206
+ if template_element_value. tail {
207
+ return template_element_value. value . raw . cow_to_ascii_uppercase ( ) ;
208
+ }
209
+ }
210
+ UNKNOWN_METHOD_NAME
211
+ }
212
+
151
213
#[ test]
152
214
fn test ( ) {
153
215
use crate :: tester:: Tester ;
@@ -182,6 +244,14 @@ fn test() {
182
244
r#"fetch('/', {body: new URLSearchParams({ data: "test" }), method: "POST"})"# ,
183
245
r#"const method = "post"; new Request(url, {method, body: "foo=bar"})"# ,
184
246
r#"const method = "post"; fetch(url, {method, body: "foo=bar"})"# ,
247
+ r#"const method = `post`; fetch(url, {method, body: "foo=bar"})"# ,
248
+ r#"const method = `po${"st"}`; fetch(url, {method, body: "foo=bar"})"# ,
249
+ r#"function foo(method: "POST" | "PUT", body: string) {
250
+ return new Request(url, {method, body});
251
+ }"# ,
252
+ "function foo(method: string, body: string) {
253
+ return new Request(url, {method, body});
254
+ }" ,
185
255
] ;
186
256
187
257
let fail = vec ! [
@@ -192,16 +262,21 @@ fn test() {
192
262
r#"fetch(url, {method: "HEAD", body})"# ,
193
263
r#"new Request(url, {method: "HEAD", body})"# ,
194
264
r#"fetch(url, {method: "head", body})"# ,
265
+ r#"fetch(url, {method: `head`, body: "foo=bar"})"# ,
195
266
r#"new Request(url, {method: "head", body})"# ,
196
267
r#"const method = "head"; new Request(url, {method, body: "foo=bar"})"# ,
197
268
r#"const method = "head"; fetch(url, {method, body: "foo=bar"})"# ,
269
+ r#"const method = `head`; fetch(url, {method, body: "foo=bar"})"# ,
198
270
r"fetch(url, {body}, extraArgument)" ,
199
271
r"new Request(url, {body}, extraArgument)" ,
200
272
r#"fetch(url, {body: undefined, body: "foo=bar"});"# ,
201
273
r#"new Request(url, {body: undefined, body: "foo=bar"});"# ,
202
274
r#"fetch(url, {method: "post", body: "foo=bar", method: "HEAD"});"# ,
203
275
r#"new Request(url, {method: "post", body: "foo=bar", method: "HEAD"});"# ,
204
276
r#"fetch('/', {body: new URLSearchParams({ data: "test" })})"# ,
277
+ r#"function foo(method: "HEAD" | "GET") {
278
+ return new Request(url, {method, body: ""});
279
+ }"# ,
205
280
] ;
206
281
207
282
Tester :: new ( NoInvalidFetchOptions :: NAME , NoInvalidFetchOptions :: PLUGIN , pass, fail)
0 commit comments