1+ use phf:: { Map , phf_map} ;
2+
3+ use oxc_allocator:: Allocator ;
14use oxc_ast:: {
2- AstKind ,
5+ AstBuilder , AstKind ,
36 ast:: {
47 Argument , BindingPatternKind , BindingProperty , CallExpression , Expression , Function ,
58 MemberExpression , PropertyKey ,
69 } ,
710} ;
11+ use oxc_codegen:: CodegenOptions ;
812use oxc_diagnostics:: OxcDiagnostic ;
913use oxc_macros:: declare_oxc_lint;
10- use oxc_span:: Span ;
11- use phf:: { Map , Set , phf_map, phf_set} ;
14+ use oxc_span:: { SPAN , Span } ;
1215
1316use crate :: { AstNode , context:: LintContext , rule:: Rule } ;
1417
1518fn prefer_keyboard_event_key_diagnostic ( span : Span , deprecated_prop : & str ) -> OxcDiagnostic {
1619 OxcDiagnostic :: warn ( format ! ( "Use `.key` instead of `.{deprecated_prop}`" ) )
17- . with_help ( "The `keyCode`, `charCode`, and `which` properties are deprecated." )
20+ . with_help ( format ! ( "The `{deprecated_prop}` property is deprecated." ) )
1821 . with_label ( span)
1922}
2023
@@ -64,96 +67,6 @@ declare_oxc_lint!(
6467 fix
6568) ;
6669
67- /// Deprecated keyboard event properties
68- const DEPRECATED_PROPERTIES : Set < & ' static str > = phf_set ! {
69- "keyCode" ,
70- "charCode" ,
71- "which" ,
72- } ;
73-
74- /// Map from key code to key name for auto-fix
75- /// Based on <https://github.com/nicolo-ribaudo/eslint-plugin-unicorn/blob/main/rules/shared/event-keys.js>
76- const KEY_CODE_TO_KEY : Map < u32 , & ' static str > = phf_map ! {
77- 8u32 => "Backspace" ,
78- 9u32 => "Tab" ,
79- 12u32 => "Clear" ,
80- 13u32 => "Enter" ,
81- 16u32 => "Shift" ,
82- 17u32 => "Control" ,
83- 18u32 => "Alt" ,
84- 19u32 => "Pause" ,
85- 20u32 => "CapsLock" ,
86- 27u32 => "Escape" ,
87- 32u32 => " " ,
88- 33u32 => "PageUp" ,
89- 34u32 => "PageDown" ,
90- 35u32 => "End" ,
91- 36u32 => "Home" ,
92- 37u32 => "ArrowLeft" ,
93- 38u32 => "ArrowUp" ,
94- 39u32 => "ArrowRight" ,
95- 40u32 => "ArrowDown" ,
96- 45u32 => "Insert" ,
97- 46u32 => "Delete" ,
98- 112u32 => "F1" ,
99- 113u32 => "F2" ,
100- 114u32 => "F3" ,
101- 115u32 => "F4" ,
102- 116u32 => "F5" ,
103- 117u32 => "F6" ,
104- 118u32 => "F7" ,
105- 119u32 => "F8" ,
106- 120u32 => "F9" ,
107- 121u32 => "F10" ,
108- 122u32 => "F11" ,
109- 123u32 => "F12" ,
110- 144u32 => "NumLock" ,
111- 145u32 => "ScrollLock" ,
112- 186u32 => ";" ,
113- 187u32 => "=" ,
114- 188u32 => "," ,
115- 189u32 => "-" ,
116- 190u32 => "." ,
117- 191u32 => "/" ,
118- 219u32 => "[" ,
119- 220u32 => "\\ " ,
120- 221u32 => "]" ,
121- 222u32 => "'" ,
122- 224u32 => "Meta" ,
123- } ;
124-
125- /// Get the key string from a key code, either from the mapping or from char code
126- fn get_key_from_code ( code : f64 ) -> Option < String > {
127- #[ expect( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
128- let code_u32 = code as u32 ;
129-
130- // First try the known key mapping
131- if let Some ( key) = KEY_CODE_TO_KEY . get ( & code_u32) {
132- return Some ( ( * key) . to_string ( ) ) ;
133- }
134-
135- // For printable characters (A-Z, 0-9, etc.), convert from char code
136- char:: from_u32 ( code_u32) . map ( |c| c. to_string ( ) )
137- }
138-
139- /// Escape a string for JavaScript output
140- fn escape_string ( s : & str ) -> String {
141- let mut result = String :: with_capacity ( s. len ( ) + 2 ) ;
142- result. push ( '\'' ) ;
143- for c in s. chars ( ) {
144- match c {
145- '\'' => result. push_str ( "\\ '" ) ,
146- '\\' => result. push_str ( "\\ \\ " ) ,
147- '\n' => result. push_str ( "\\ n" ) ,
148- '\r' => result. push_str ( "\\ r" ) ,
149- '\t' => result. push_str ( "\\ t" ) ,
150- _ => result. push ( c) ,
151- }
152- }
153- result. push ( '\'' ) ;
154- result
155- }
156-
15770impl Rule for PreferKeyboardEventKey {
15871 fn run < ' a > ( & self , node : & AstNode < ' a > , ctx : & LintContext < ' a > ) {
15972 match node. kind ( ) {
@@ -177,7 +90,7 @@ impl PreferKeyboardEventKey {
17790 ) {
17891 // Check if this is a deprecated property name
17992 let property_name = member_expr. property . name . as_str ( ) ;
180- if !DEPRECATED_PROPERTIES . contains ( property_name) {
93+ if !is_deprecated_property ( property_name) {
18194 return ;
18295 }
18396
@@ -205,16 +118,16 @@ impl PreferKeyboardEventKey {
205118 ctx : & LintContext < ' a > ,
206119 ) {
207120 // Get the property name from the key
208- let key_name = match & prop. key {
209- PropertyKey :: StaticIdentifier ( ident) => ident. name . as_str ( ) ,
210- _ => return ,
121+ let PropertyKey :: StaticIdentifier ( key_ident) = & prop. key else {
122+ return ;
211123 } ;
124+ let key_name = key_ident. name . as_str ( ) ;
212125
213126 // Get the value name (the identifier being introduced)
214- let value_name = match & prop. value . kind {
215- BindingPatternKind :: BindingIdentifier ( ident) => ident. name . as_str ( ) ,
216- _ => return ,
127+ let BindingPatternKind :: BindingIdentifier ( value_ident) = & prop. value . kind else {
128+ return ;
217129 } ;
130+ let value_name = value_ident. name . as_str ( ) ;
218131
219132 // Determine which property name we're checking and if we should report
220133 // There are several cases:
@@ -223,13 +136,11 @@ impl PreferKeyboardEventKey {
223136 // 3. Non-shorthand: { a: keyCode } = event -> report keyCode (introducing deprecated variable)
224137 let ( should_report, deprecated_name) = if prop. shorthand {
225138 // Shorthand case: key and value are the same
226- ( DEPRECATED_PROPERTIES . contains ( key_name) , key_name)
227- } else if DEPRECATED_PROPERTIES . contains ( key_name)
228- && !DEPRECATED_PROPERTIES . contains ( value_name)
229- {
139+ ( is_deprecated_property ( key_name) , key_name)
140+ } else if is_deprecated_property ( key_name) && !is_deprecated_property ( value_name) {
230141 // { keyCode: abc } = event -> OK, renaming away from deprecated
231142 ( false , key_name)
232- } else if DEPRECATED_PROPERTIES . contains ( value_name) {
143+ } else if is_deprecated_property ( value_name) {
233144 // { a: keyCode } = event -> bad, introducing deprecated variable name
234145 ( true , value_name)
235146 } else {
@@ -265,13 +176,11 @@ impl PreferKeyboardEventKey {
265176 return ;
266177 }
267178
268- // Get the span for the value (the identifier being introduced)
269- let value_span = match & prop. value . kind {
270- BindingPatternKind :: BindingIdentifier ( ident) => ident. span ,
271- _ => return ,
179+ let BindingPatternKind :: BindingIdentifier ( ident) = & prop. value . kind else {
180+ return ;
272181 } ;
273182
274- ctx. diagnostic ( prefer_keyboard_event_key_diagnostic ( value_span , deprecated_name) ) ;
183+ ctx. diagnostic ( prefer_keyboard_event_key_diagnostic ( ident . span , deprecated_name) ) ;
275184 }
276185
277186 /// Check if this property is directly in the event parameter destructuring
@@ -497,13 +406,24 @@ impl PreferKeyboardEventKey {
497406 } ;
498407
499408 // Apply fix
500- let escaped_key = escape_string ( & key_name) ;
501409 ctx. diagnostic_with_fix (
502410 prefer_keyboard_event_key_diagnostic ( property_span, property_name) ,
503411 |fixer| {
412+ let mut codegen = fixer
413+ . codegen ( )
414+ . with_options ( CodegenOptions { single_quote : true , ..Default :: default ( ) } ) ;
415+ let alloc = Allocator :: default ( ) ;
416+ let ast = AstBuilder :: new ( & alloc) ;
417+ codegen. print_expression ( & ast. expression_string_literal (
418+ SPAN ,
419+ ast. atom ( & key_name) ,
420+ None ,
421+ ) ) ;
422+ let key_str = codegen. into_source_text ( ) ;
423+
504424 let mut fix = fixer. new_fix_with_capacity ( 2 ) ;
505425 fix. push ( fixer. replace ( property_span, "key" ) ) ;
506- fix. push ( fixer. replace ( number_span, escaped_key . clone ( ) ) ) ;
426+ fix. push ( fixer. replace ( number_span, key_str ) ) ;
507427 fix. with_message ( format ! ( "Replace `.{property_name}` with `.key`" ) )
508428 } ,
509429 ) ;
@@ -516,6 +436,76 @@ enum CallbackFunction<'a> {
516436 Regular ( & ' a Function < ' a > ) ,
517437}
518438
439+ /// Check if a property name is a deprecated keyboard event property
440+ fn is_deprecated_property ( name : & str ) -> bool {
441+ matches ! ( name, "keyCode" | "charCode" | "which" )
442+ }
443+
444+ /// Map from key code to key name for auto-fix
445+ /// Based on <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/shared/event-keys.js>
446+ const KEY_CODE_TO_KEY : Map < u32 , & ' static str > = phf_map ! {
447+ 8u32 => "Backspace" ,
448+ 9u32 => "Tab" ,
449+ 12u32 => "Clear" ,
450+ 13u32 => "Enter" ,
451+ 16u32 => "Shift" ,
452+ 17u32 => "Control" ,
453+ 18u32 => "Alt" ,
454+ 19u32 => "Pause" ,
455+ 20u32 => "CapsLock" ,
456+ 27u32 => "Escape" ,
457+ 32u32 => " " ,
458+ 33u32 => "PageUp" ,
459+ 34u32 => "PageDown" ,
460+ 35u32 => "End" ,
461+ 36u32 => "Home" ,
462+ 37u32 => "ArrowLeft" ,
463+ 38u32 => "ArrowUp" ,
464+ 39u32 => "ArrowRight" ,
465+ 40u32 => "ArrowDown" ,
466+ 45u32 => "Insert" ,
467+ 46u32 => "Delete" ,
468+ 112u32 => "F1" ,
469+ 113u32 => "F2" ,
470+ 114u32 => "F3" ,
471+ 115u32 => "F4" ,
472+ 116u32 => "F5" ,
473+ 117u32 => "F6" ,
474+ 118u32 => "F7" ,
475+ 119u32 => "F8" ,
476+ 120u32 => "F9" ,
477+ 121u32 => "F10" ,
478+ 122u32 => "F11" ,
479+ 123u32 => "F12" ,
480+ 144u32 => "NumLock" ,
481+ 145u32 => "ScrollLock" ,
482+ 186u32 => ";" ,
483+ 187u32 => "=" ,
484+ 188u32 => "," ,
485+ 189u32 => "-" ,
486+ 190u32 => "." ,
487+ 191u32 => "/" ,
488+ 219u32 => "[" ,
489+ 220u32 => "\\ " ,
490+ 221u32 => "]" ,
491+ 222u32 => "'" ,
492+ 224u32 => "Meta" ,
493+ } ;
494+
495+ /// Get the key string from a key code, either from the mapping or from char code
496+ fn get_key_from_code ( code : f64 ) -> Option < String > {
497+ #[ expect( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
498+ let code_u32 = code as u32 ;
499+
500+ // First try the known key mapping
501+ if let Some ( key) = KEY_CODE_TO_KEY . get ( & code_u32) {
502+ return Some ( ( * key) . to_string ( ) ) ;
503+ }
504+
505+ // For printable characters (A-Z, 0-9, etc.), convert from char code
506+ char:: from_u32 ( code_u32) . map ( |c| c. to_string ( ) )
507+ }
508+
519509#[ test]
520510fn test ( ) {
521511 use crate :: tester:: Tester ;
0 commit comments