fix(linter/unicorn/prefer-string-raw): check object property values (#27758)

Values such as `{ b: "a\\b" }` were skipped whenever the property key was not a string literal. Only exempt non-computed string-literal keys so property values are reported and fixed consistently with Unicorn.

Addresses report 8 (`unicorn/prefer-string-raw`) in #27746.
This commit is contained in:
camc314 committed 2026-10-16 09:19:16 +00:00
1 parent ddea86b023
commit 9261df700b
2 files changed
+107 -5

No files matched your search

@@ -98,11 +98,10 @@ impl Rule for PreferStringRaw {
return;
}
AstKind::ObjectProperty(prop) => {
let PropertyKey::StringLiteral(key) = &prop.key else {
return;
};
if !prop.computed && string_literal.span == key.span {
if let PropertyKey::StringLiteral(key) = &prop.key
&& !prop.computed
&& string_literal.span == key.span
{
return;
}
}
@@ -264,6 +263,7 @@ fn test() {
r#"interface EventSchemas { "foo\\bar": Record<string, any>; }"#,
r#"interface Methods { "foo\\bar"(): void; }"#,
r#"let x: "a\\b";"#,
r"const o = { newline: 'a\\b\nc', trail: 'a\\', interpolation: 'a\\b${foo}', tick: 'a\\b`' };",
];
let fail = vec![
@@ -280,6 +280,12 @@ fn test() {
r"for (const f of'a\\b') {}",
// Non-ASCII characters without trailing backslash - should be fixed
r"const a = 'c:\\someöäü\\path';",
r#"export const o = { b: "a\\b" };"#,
r#"export const s = "a\\b";"#,
r#"export const p = { "k": "a\\b" };"#,
r#"const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };"#,
r#"const o = { "a\\b": "c\\d", ["a\\b"]: "c\\d" };"#,
r"const o = { blank: ' \\t' };",
];
let fix = vec![
@@ -302,6 +308,18 @@ fn test() {
(r"const a = () => void'a\\b';", r"const a = () => void String.raw`a\b`;"),
// Non-ASCII characters
(r"const a = 'c:\\someöäü\\path';", r"const a = String.raw`c:\someöäü\path`;"),
(r#"export const o = { b: "a\\b" };"#, r"export const o = { b: String.raw`a\b` };"),
(r#"export const s = "a\\b";"#, r"export const s = String.raw`a\b`;"),
(r#"export const p = { "k": "a\\b" };"#, r#"export const p = { "k": String.raw`a\b` };"#),
(
r#"const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };"#,
r#"const o = { 0: String.raw`a\b`, [key]: String.raw`a\b`, [0]: String.raw`a\b`, 1n: String.raw`a\b`, ["k"]: String.raw`a\b` };"#,
),
(
r#"const o = { "a\\b": "c\\d", ["a\\b"]: "c\\d" };"#,
r#"const o = { "a\\b": String.raw`c\d`, [String.raw`a\b`]: String.raw`c\d` };"#,
),
(r"const o = { blank: ' \\t' };", r"const o = { blank: String.raw` \t` };"),
];
Tester::new(PreferStringRaw::NAME, PreferStringRaw::PLUGIN, pass, fail)
@@ -85,3 +85,87 @@ source: crates/oxc_linter/src/tester.rs
· ───────────────────
╰────
help: Replace `'c:\\someöäü\\path'` with `String.raw`c:\someöäü\path``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:23]
1 │ export const o = { b: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:18]
1 │ export const s = "a\\b";
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:25]
1 │ export const p = { "k": "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:16]
1 │ const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:31]
1 │ const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:44]
1 │ const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:56]
1 │ const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:71]
1 │ const o = { 0: "a\\b", [key]: "a\\b", [0]: "a\\b", 1n: "a\\b", ["k"]: "a\\b" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:21]
1 │ const o = { "a\\b": "c\\d", ["a\\b"]: "c\\d" };
· ──────
╰────
help: Replace `"c\\d"` with `String.raw`c\d``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:30]
1 │ const o = { "a\\b": "c\\d", ["a\\b"]: "c\\d" };
· ──────
╰────
help: Replace `"a\\b"` with `String.raw`a\b``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:39]
1 │ const o = { "a\\b": "c\\d", ["a\\b"]: "c\\d" };
· ──────
╰────
help: Replace `"c\\d"` with `String.raw`c\d``.
⚠ unicorn(prefer-string-raw): `String.raw` should be used to avoid escaping `\`.
╭─[prefer_string_raw.tsx:1:20]
1 │ const o = { blank: ' \\t' };
· ──────
╰────
help: Replace `' \\t'` with `String.raw` \t``.