diff --git a/crates/oxc_linter/src/rules/eslint/valid_typeof.rs b/crates/oxc_linter/src/rules/eslint/valid_typeof.rs index 350af9ec19c66..c960b3a445663 100644 --- a/crates/oxc_linter/src/rules/eslint/valid_typeof.rs +++ b/crates/oxc_linter/src/rules/eslint/valid_typeof.rs @@ -21,7 +21,7 @@ fn not_string(help: Option<&'static str>, span: Span) -> OxcDiagnostic { d } -fn invalid_value(help: Option<&'static str>, span: Span) -> OxcDiagnostic { +fn invalid_value(help: Option, span: Span) -> OxcDiagnostic { let mut d = OxcDiagnostic::warn("Invalid `typeof` comparison value.").with_label(span); if let Some(x) = help { d = d.with_help(x); @@ -123,7 +123,9 @@ impl Rule for ValidTypeof { if let Expression::StringLiteral(lit) = sibling { if !VALID_TYPES.contains(&lit.value.as_str()) { - ctx.diagnostic(invalid_value(None, sibling.span())); + let help = get_typo_suggestion(lit.value.as_str()) + .map(|suggestion| format!("Did you mean `\"{suggestion}\"`?")); + ctx.diagnostic(invalid_value(help, sibling.span())); } return; } @@ -132,7 +134,9 @@ impl Rule for ValidTypeof { && let Some(quasi) = template.single_quasi() { if !VALID_TYPES.contains(&quasi.as_str()) { - ctx.diagnostic(invalid_value(None, sibling.span())); + let help = get_typo_suggestion(quasi.as_str()) + .map(|suggestion| format!("Did you mean `\"{suggestion}\"`?")); + ctx.diagnostic(invalid_value(help, sibling.span())); } return; } @@ -146,7 +150,7 @@ impl Rule for ValidTypeof { not_string(Some("Use `\"undefined\"` instead of `undefined`."), sibling.span()) } else { invalid_value( - Some("Use `\"undefined\"` instead of `undefined`."), + Some("Use `\"undefined\"` instead of `undefined`.".to_string()), sibling.span(), ) }, @@ -166,6 +170,29 @@ impl Rule for ValidTypeof { const VALID_TYPES: [&str; 8] = ["bigint", "boolean", "function", "number", "object", "string", "symbol", "undefined"]; +/// Check for common misspellings of typeof values and return a suggestion. +fn get_typo_suggestion(value: &str) -> Option<&'static str> { + // spellchecker:off + match value { + "strnig" | "stirng" | "sting" | "strng" | "srting" | "srtring" | "strning" => { + Some("string") + } + "umdefined" | "undefimed" | "underfined" | "undefinied" | "undefied" | "undeffined" + | "undefind" | "undifined" | "udefined" | "undfined" => Some("undefined"), + "fucntion" | "funtion" | "fuction" | "functon" | "funcion" | "funciton" | "functin" + | "funcitn" | "funcrion" => Some("function"), + "obejct" | "objcet" | "obect" | "objetc" | "objetct" | "objet" | "objec" => Some("object"), + "bolean" | "booleen" | "boolen" | "boolaen" | "booelan" | "bollean" => Some("boolean"), + "nunber" | "nubmer" | "numbre" | "numver" | "numbr" | "numebr" | "nmber" => Some("number"), + "symol" | "symblo" | "simbole" | "synbol" | "symboll" | "symbal" => Some("symbol"), + "biigint" | "bignt" | "biignt" | "bigimit" | "bignit" | "bigit" | "begint" => { + Some("bigint") + } + _ => None, + } + // spellchecker:on +} + #[test] fn test() { use crate::tester::Tester; @@ -240,6 +267,13 @@ fn test() { "typeof foo === `${string}`", Some(serde_json::json!([{ "requireStringLiterals": true }])), ), + // Typo suggestions for each valid typeof type + ("typeof foo === 'biigint'", None), // spellchecker:disable-line + ("typeof foo === 'bolean'", None), // spellchecker:disable-line + ("typeof foo === 'fucntion'", None), // spellchecker:disable-line + ("typeof foo === 'nunber'", None), // spellchecker:disable-line + ("typeof foo === 'obejct'", None), // spellchecker:disable-line + ("typeof foo === 'symol'", None), // spellchecker:disable-line ]; let fix = vec![("typeof foo === undefined", r#"typeof foo === "undefined""#)]; diff --git a/crates/oxc_linter/src/snapshots/eslint_valid_typeof.snap b/crates/oxc_linter/src/snapshots/eslint_valid_typeof.snap index a17c24da274b3..536008aec9b68 100644 --- a/crates/oxc_linter/src/snapshots/eslint_valid_typeof.snap +++ b/crates/oxc_linter/src/snapshots/eslint_valid_typeof.snap @@ -6,78 +6,91 @@ source: crates/oxc_linter/src/tester.rs 1 │ typeof foo === 'strnig' · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:1] 1 │ 'strnig' === typeof foo · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:20] 1 │ if (typeof bar === 'umdefined') {} · ─────────── ╰──── + help: Did you mean `"undefined"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:16] 1 │ typeof foo !== 'strnig' · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:1] 1 │ 'strnig' !== typeof foo · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:20] 1 │ if (typeof bar !== 'umdefined') {} · ─────────── ╰──── + help: Did you mean `"undefined"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:15] 1 │ typeof foo != 'strnig' · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:1] 1 │ 'strnig' != typeof foo · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:19] 1 │ if (typeof bar != 'umdefined') {} · ─────────── ╰──── + help: Did you mean `"undefined"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:15] 1 │ typeof foo == 'strnig' · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:1] 1 │ 'strnig' == typeof foo · ──────── ╰──── + help: Did you mean `"string"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:19] 1 │ if (typeof bar == 'umdefined') {} · ─────────── ╰──── + help: Did you mean `"undefined"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:20] 1 │ if (typeof bar === `umdefined`) {} · ─────────── ╰──── + help: Did you mean `"undefined"`? ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. ╭─[valid_typeof.tsx:1:15] @@ -130,3 +143,45 @@ source: crates/oxc_linter/src/tester.rs 1 │ typeof foo === `${string}` · ─────────── ╰──── + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'biigint' + · ───────── + ╰──── + help: Did you mean `"bigint"`? + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'bolean' + · ──────── + ╰──── + help: Did you mean `"boolean"`? + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'fucntion' + · ────────── + ╰──── + help: Did you mean `"function"`? + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'nunber' + · ──────── + ╰──── + help: Did you mean `"number"`? + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'obejct' + · ──────── + ╰──── + help: Did you mean `"object"`? + + ⚠ eslint(valid-typeof): Invalid `typeof` comparison value. + ╭─[valid_typeof.tsx:1:16] + 1 │ typeof foo === 'symol' + · ─────── + ╰──── + help: Did you mean `"symbol"`?