From c57882e1c3b51f00b94da4ed9b40d5cf2e4d6847 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Sat, 23 Nov 2024 04:52:57 -0500 Subject: [PATCH] feat: rule no-unnormalized-keys (#63) * feat: rule no-unormalized-keys Fixes #32 * Add no-unnormalized-keys to plugin exports * Cleanup tests and refactor for clarity * Output key into error message and update tests * Update README * Switch to object option * Re-add eslint.test.js * Update src/rules/no-unnormalized-keys.js Co-authored-by: Milos Djermanovic * Update src/rules/no-unnormalized-keys.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Bradley Meck Farias Co-authored-by: Milos Djermanovic --- README.md | 1 + src/index.js | 3 + src/rules/no-unnormalized-keys.js | 54 ++++++++++++++ tests/rules/no-unnormalized-keys.test.js | 90 ++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/rules/no-unnormalized-keys.js create mode 100644 tests/rules/no-unnormalized-keys.test.js diff --git a/README.md b/README.md index 8596b0b..d6f4017 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ export default [ - `no-duplicate-keys` - warns when there are two keys in an object with the same text. - `no-empty-keys` - warns when there is a key in an object that is an empty string or contains only whitespace (note: `package-lock.json` uses empty keys intentionally) - `no-unsafe-values` - warns on values that are unsafe for interchange, such as numbers outside safe range or lone surrogates. +- `no-unnormalized-keys` - warns on keys containing [unnormalized characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize#description). You can optionally specify the normalization form via `{ form: "form_name" }`, where `form_name` can be any of `"NFC"`, `"NFD"`, `"NFKC"`, or `"NFKD"`. ## Configuration Comments diff --git a/src/index.js b/src/index.js index 6181006..3ca4f8c 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import { JSONSourceCode } from "./languages/json-source-code.js"; import noDuplicateKeys from "./rules/no-duplicate-keys.js"; import noEmptyKeys from "./rules/no-empty-keys.js"; import noUnsafeValues from "./rules/no-unsafe-values.js"; +import noUnnormalizedKeys from "./rules/no-unnormalized-keys.js"; //----------------------------------------------------------------------------- // Plugin @@ -31,6 +32,7 @@ const plugin = { "no-duplicate-keys": noDuplicateKeys, "no-empty-keys": noEmptyKeys, "no-unsafe-values": noUnsafeValues, + "no-unnormalized-keys": noUnnormalizedKeys, }, configs: { recommended: { @@ -39,6 +41,7 @@ const plugin = { "json/no-duplicate-keys": "error", "json/no-empty-keys": "error", "json/no-unsafe-values": "error", + "json/no-unnormalized-keys": "error", }), }, }, diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js new file mode 100644 index 0000000..716959f --- /dev/null +++ b/src/rules/no-unnormalized-keys.js @@ -0,0 +1,54 @@ +/** + * @fileoverview Rule to detect unnormalized keys in JSON. + * @author Bradley Meck Farias + */ + +export default { + meta: { + type: /** @type {const} */ ("problem"), + + docs: { + description: "Disallow JSON keys that are not normalized", + }, + + messages: { + unnormalizedKey: "Unnormalized key '{{key}}' found.", + }, + + schema: [ + { + type: "object", + properties: { + form: { + enum: ["NFC", "NFD", "NFKC", "NFKD"], + }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const normalization = context.options.length + ? text => text.normalize(context.options[0].form) + : text => text.normalize(); + return { + Member(node) { + const key = + node.name.type === "String" + ? node.name.value + : node.name.name; + + if (normalization(key) !== key) { + context.report({ + loc: node.name.loc, + messageId: "unnormalizedKey", + data: { + key, + }, + }); + } + }, + }; + }, +}; diff --git a/tests/rules/no-unnormalized-keys.test.js b/tests/rules/no-unnormalized-keys.test.js new file mode 100644 index 0000000..02f3fbf --- /dev/null +++ b/tests/rules/no-unnormalized-keys.test.js @@ -0,0 +1,90 @@ +/** + * @fileoverview Tests for no-unnormalized-keys rule. + * @author Bradley Meck Farias + */ + +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import rule from "../../src/rules/no-unnormalized-keys.js"; +import json from "../../src/index.js"; +import { RuleTester } from "eslint"; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + plugins: { + json, + }, + language: "json/json", +}); + +const o = "\u1E9B\u0323"; + +ruleTester.run("no-unnormalized-keys", rule, { + valid: [ + `{"${o}":"NFC"}`, + { + code: `{"${o}":"NFC"}`, + options: [{ form: "NFC" }], + }, + { + code: `{"${o.normalize("NFD")}":"NFD"}`, + options: [{ form: "NFD" }], + }, + { + code: `{"${o.normalize("NFKC")}":"NFKC"}`, + options: [{ form: "NFKC" }], + }, + { + code: `{"${o.normalize("NFKD")}":"NFKD"}`, + options: [{ form: "NFKD" }], + }, + ], + invalid: [ + { + code: `{"${o.normalize("NFD")}":"NFD"}`, + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFD") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: `{${o.normalize("NFD")}:"NFD"}`, + language: "json/json5", + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFD") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, + { + code: `{"${o.normalize("NFKC")}":"NFKC"}`, + options: [{ form: "NFKD" }], + errors: [ + { + messageId: "unnormalizedKey", + data: { key: o.normalize("NFKC") }, + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, + ], +});