Skip to content

Commit 7c241e0

Browse files
committed
u
1 parent 552b0da commit 7c241e0

File tree

2 files changed

+156
-166
lines changed

2 files changed

+156
-166
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_keyboard_event_key.rs

Lines changed: 103 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
use phf::{Map, phf_map};
2+
3+
use oxc_allocator::Allocator;
14
use 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;
812
use oxc_diagnostics::OxcDiagnostic;
913
use 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

1316
use crate::{AstNode, context::LintContext, rule::Rule};
1417

1518
fn 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-
15770
impl 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]
520510
fn test() {
521511
use crate::tester::Tester;

0 commit comments

Comments
 (0)