Skip to content

Commit

Permalink
feat: rule no-unnormalized-keys (#63)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update src/rules/no-unnormalized-keys.js

Co-authored-by: Milos Djermanovic <[email protected]>

---------

Co-authored-by: Bradley Meck Farias <[email protected]>
Co-authored-by: Milos Djermanovic <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2024
1 parent ede448d commit c57882e
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +32,7 @@ const plugin = {
"no-duplicate-keys": noDuplicateKeys,
"no-empty-keys": noEmptyKeys,
"no-unsafe-values": noUnsafeValues,
"no-unnormalized-keys": noUnnormalizedKeys,
},
configs: {
recommended: {
Expand All @@ -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",
}),
},
},
Expand Down
54 changes: 54 additions & 0 deletions src/rules/no-unnormalized-keys.js
Original file line number Diff line number Diff line change
@@ -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,
},
});
}
},
};
},
};
90 changes: 90 additions & 0 deletions tests/rules/no-unnormalized-keys.test.js
Original file line number Diff line number Diff line change
@@ -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,
},
],
},
],
});

0 comments on commit c57882e

Please sign in to comment.