From 17318be2c20e20ad99f42a3f2708bfbd473c14e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Sat, 20 Sep 2025 22:44:37 +0900 Subject: [PATCH 1/7] feat: implement autofix for `no-unnormalized-keys` --- src/rules/no-unnormalized-keys.js | 9 ++++++ tests/rules/no-unnormalized-keys.test.js | 36 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index 8794cff..fe84f3b 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -24,6 +24,8 @@ const rule = { meta: { type: "problem", + fixable: "code", + docs: { recommended: true, description: "Disallow JSON keys that are not normalized", @@ -55,6 +57,7 @@ const rule = { create(context) { const [{ form }] = context.options; + const { sourceCode } = context; return { Member(node) { @@ -70,6 +73,12 @@ const rule = { data: { key, }, + fix(fixer) { + return fixer.replaceTextRange( + node.name.range, + sourceCode.getText(node.name).normalize(form), // Quotes cannot be normalized, so it's safe. + ); + }, }); } }, diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js index 6b1bde5..09a8e09 100644 --- a/tests/rules/no-unnormalized-keys.test.js +++ b/tests/rules/no-unnormalized-keys.test.js @@ -47,6 +47,7 @@ ruleTester.run("no-unnormalized-keys", rule, { invalid: [ { code: `{"${o.normalize("NFD")}":"NFD"}`, + output: `{"${o.normalize("NFC")}":"NFD"}`, errors: [ { messageId: "unnormalizedKey", @@ -60,6 +61,7 @@ ruleTester.run("no-unnormalized-keys", rule, { }, { code: `{"${o.normalize("NFD")}":"NFD"}`, + output: `{"${o.normalize("NFC")}":"NFD"}`, language: "json/jsonc", errors: [ { @@ -74,6 +76,7 @@ ruleTester.run("no-unnormalized-keys", rule, { }, { code: `{${o.normalize("NFD")}:"NFD"}`, + output: `{${o.normalize("NFC")}:"NFD"}`, language: "json/json5", errors: [ { @@ -88,6 +91,7 @@ ruleTester.run("no-unnormalized-keys", rule, { }, { code: `{"${o.normalize("NFKC")}":"NFKC"}`, + output: `{"${o.normalize("NFKD")}":"NFKC"}`, options: [{ form: "NFKD" }], errors: [ { @@ -100,5 +104,37 @@ ruleTester.run("no-unnormalized-keys", rule, { }, ], }, + { + code: `{"${o.normalize("NFKC")}":"NFKC"}`, + output: `{"${o.normalize("NFKD")}":"NFKC"}`, + language: "json/jsonc", + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, + { + code: `{${o.normalize("NFKC")}:"NFKC"}`, + output: `{${o.normalize("NFKD")}:"NFKC"}`, + language: "json/json5", + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + ], + }, ], }); From 42c637aa4214d6875c681c820e4a2c4531ace8bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Sat, 20 Sep 2025 22:53:06 +0900 Subject: [PATCH 2/7] wip --- tests/rules/no-unnormalized-keys.test.js | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js index 09a8e09..32b6ad3 100644 --- a/tests/rules/no-unnormalized-keys.test.js +++ b/tests/rules/no-unnormalized-keys.test.js @@ -74,6 +74,36 @@ ruleTester.run("no-unnormalized-keys", rule, { }, ], }, + { + code: `{"${o.normalize("NFD")}":"NFD"}`, + output: `{"${o.normalize("NFC")}":"NFD"}`, + language: "json/json5", + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFD") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: `{'${o.normalize("NFD")}':'NFD'}`, + output: `{'${o.normalize("NFC")}':'NFD'}`, + language: "json/json5", + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFD") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 7, + }, + ], + }, { code: `{${o.normalize("NFD")}:"NFD"}`, output: `{${o.normalize("NFC")}:"NFD"}`, @@ -120,6 +150,38 @@ ruleTester.run("no-unnormalized-keys", rule, { }, ], }, + { + code: `{"${o.normalize("NFKC")}":"NFKC"}`, + output: `{"${o.normalize("NFKD")}":"NFKC"}`, + language: "json/json5", + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, + { + code: `{'${o.normalize("NFKC")}':"NFKC"}`, + output: `{'${o.normalize("NFKD")}':"NFKC"}`, + language: "json/json5", + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, { code: `{${o.normalize("NFKC")}:"NFKC"}`, output: `{${o.normalize("NFKD")}:"NFKC"}`, From 78c0ce5a7bfd002612f4221fed5b12a0efdfd9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Sat, 20 Sep 2025 23:09:39 +0900 Subject: [PATCH 3/7] wip: pin `rollup` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d2b700a..463bb98 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "mdast-util-from-markdown": "^2.0.2", "mocha": "^11.3.0", "prettier": "^3.4.1", - "rollup": "^4.41.0", + "rollup": "4.51.0", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-delete": "^3.0.1", "typescript": "^5.9.2", From 58a24c71ec2840e84ab2db12afc4f05575deece9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Tue, 14 Oct 2025 20:59:52 +0900 Subject: [PATCH 4/7] wip: add more test cases --- tests/rules/no-unnormalized-keys.test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js index 32b6ad3..5dde838 100644 --- a/tests/rules/no-unnormalized-keys.test.js +++ b/tests/rules/no-unnormalized-keys.test.js @@ -23,6 +23,10 @@ const ruleTester = new RuleTester({ }); const o = "\u1E9B\u0323"; +const escapedNfcO = "\\u1E9B\\u0323"; +const escapedNfdO = "\\u017F\\u0323"; +const escapedNfkcO = "\\u1E69"; +const escapedNfkdO = "\\u0073\\u0323"; ruleTester.run("no-unnormalized-keys", rule, { valid: [ @@ -43,6 +47,24 @@ ruleTester.run("no-unnormalized-keys", rule, { code: `{"${o.normalize("NFKD")}":"NFKD"}`, options: [{ form: "NFKD" }], }, + // escaped form + `{"${escapedNfcO}":"NFC"}`, + { + code: `{"${escapedNfcO}":"NFC"}`, + options: [{ form: "NFC" }], + }, + { + code: `{"${escapedNfdO}":"NFD"}`, + options: [{ form: "NFD" }], + }, + { + code: `{"${escapedNfkcO}":"NFKC"}`, + options: [{ form: "NFKC" }], + }, + { + code: `{"${escapedNfkdO}":"NFKD"}`, + options: [{ form: "NFKD" }], + }, ], invalid: [ { @@ -198,5 +220,6 @@ ruleTester.run("no-unnormalized-keys", rule, { }, ], }, + // escaped form ], }); From 1bfcd7dcddd9bf744a86af1d15e33971386ac790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Tue, 14 Oct 2025 21:29:18 +0900 Subject: [PATCH 5/7] wip --- src/rules/no-unnormalized-keys.js | 10 +++++++--- tests/rules/no-unnormalized-keys.test.js | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index fe84f3b..aa6c203 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -57,7 +57,6 @@ const rule = { create(context) { const [{ form }] = context.options; - const { sourceCode } = context; return { Member(node) { @@ -75,8 +74,13 @@ const rule = { }, fix(fixer) { return fixer.replaceTextRange( - node.name.range, - sourceCode.getText(node.name).normalize(form), // Quotes cannot be normalized, so it's safe. + node.name.type === "String" + ? [ + node.name.range[0] + 1, + node.name.range[1] - 1, + ] + : node.name.range, + key.normalize(form), ); }, }); diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js index 5dde838..e273bfe 100644 --- a/tests/rules/no-unnormalized-keys.test.js +++ b/tests/rules/no-unnormalized-keys.test.js @@ -24,9 +24,9 @@ const ruleTester = new RuleTester({ const o = "\u1E9B\u0323"; const escapedNfcO = "\\u1E9B\\u0323"; -const escapedNfdO = "\\u017F\\u0323"; +const escapedNfdO = "\\u017F\\u0323\\u0307"; const escapedNfkcO = "\\u1E69"; -const escapedNfkdO = "\\u0073\\u0323"; +const escapedNfkdO = "\\u0073\\u0323\\u0307"; ruleTester.run("no-unnormalized-keys", rule, { valid: [ @@ -221,5 +221,19 @@ ruleTester.run("no-unnormalized-keys", rule, { ], }, // escaped form + { + code: `{"${escapedNfdO}":"NFD"}`, + output: `{"${o.normalize("NFC")}":"NFD"}`, + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFD") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 22, + }, + ], + }, ], }); From 16a61a1bcd0094361f30a8a8787864bc164a099a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Tue, 14 Oct 2025 21:34:22 +0900 Subject: [PATCH 6/7] wip --- src/rules/no-unnormalized-keys.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index aa6c203..052f017 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -59,28 +59,23 @@ const rule = { const [{ form }] = context.options; return { - Member(node) { - const key = - node.name.type === "String" - ? node.name.value - : node.name.name; + Member({ name }) { + const key = name.type === "String" ? name.value : name.name; + const normalizedKey = key.normalize(form); - if (key.normalize(form) !== key) { + if (normalizedKey !== key) { context.report({ - loc: node.name.loc, + loc: name.loc, messageId: "unnormalizedKey", data: { key, }, fix(fixer) { return fixer.replaceTextRange( - node.name.type === "String" - ? [ - node.name.range[0] + 1, - node.name.range[1] - 1, - ] - : node.name.range, - key.normalize(form), + name.type === "String" + ? [name.range[0] + 1, name.range[1] - 1] + : name.range, + normalizedKey, ); }, }); From 5e5e7d8bf6f4f9fbb83e9034c4ffc01a42487c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Wed, 15 Oct 2025 19:39:35 +0900 Subject: [PATCH 7/7] wip: add more test --- tests/rules/no-unnormalized-keys.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js index e273bfe..a4f5cc6 100644 --- a/tests/rules/no-unnormalized-keys.test.js +++ b/tests/rules/no-unnormalized-keys.test.js @@ -235,5 +235,20 @@ ruleTester.run("no-unnormalized-keys", rule, { }, ], }, + { + code: `{"${escapedNfkcO}":"NFKC"}`, + output: `{"${o.normalize("NFKD")}":"NFKC"}`, + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 10, + }, + ], + }, ], });