From b222dea6fa72000e275f8d7554825da97e435ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Tue, 4 Nov 2025 23:52:24 +0900 Subject: [PATCH 1/8] fix: display raw key in the message --- src/rules/no-duplicate-keys.js | 7 ++- src/rules/sort-keys.js | 2 +- tests/rules/no-duplicate-keys.test.js | 63 +++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/rules/no-duplicate-keys.js b/src/rules/no-duplicate-keys.js index fef2ffa..786e07c 100644 --- a/src/rules/no-duplicate-keys.js +++ b/src/rules/no-duplicate-keys.js @@ -53,19 +53,24 @@ const rule = { node.name.type === "String" ? node.name.value : node.name.name; + const rawKey = + node.name.type === "String" + ? context.sourceCode.getText(node.name, -1, -1) + : context.sourceCode.getText(node.name); if (keys.has(key)) { context.report({ loc: node.name.loc, messageId: "duplicateKey", data: { - key, + key: rawKey, }, }); } else { keys.set(key, node); } }, + "Object:exit"() { keys = objectKeys.pop(); }, diff --git a/src/rules/sort-keys.js b/src/rules/sort-keys.js index ce4ceec..2c6bc28 100644 --- a/src/rules/sort-keys.js +++ b/src/rules/sort-keys.js @@ -114,7 +114,7 @@ const rule = { messages: { sortKeys: - "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.", + "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.", // TODO }, schema: [ diff --git a/tests/rules/no-duplicate-keys.test.js b/tests/rules/no-duplicate-keys.test.js index e84bd87..65f0076 100644 --- a/tests/rules/no-duplicate-keys.test.js +++ b/tests/rules/no-duplicate-keys.test.js @@ -151,12 +151,67 @@ ruleTester.run("no-duplicate-keys", rule, { }, ], }, + { + code: '{"foot": 1, "fo\\u006ft": 2}', + errors: [ + { + messageId: "duplicateKey", + data: { key: "fo\\u006ft" }, + line: 1, + column: 13, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: '{"foot": 1, "fo\\u006ft": 2}', + language: "json/jsonc", + errors: [ + { + messageId: "duplicateKey", + data: { key: "fo\\u006ft" }, + line: 1, + column: 13, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: '{"foot": 1, "fo\\u006ft": 2}', + language: "json/json5", + errors: [ + { + messageId: "duplicateKey", + data: { key: "fo\\u006ft" }, + line: 1, + column: 13, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "{foot: 1, fo\\u006ft: 2}", + language: "json/json5", + errors: [ + { + messageId: "duplicateKey", + data: { key: "fo\\u006ft" }, + line: 1, + column: 11, + endLine: 1, + endColumn: 20, + }, + ], + }, { code: '{"f\\u006fot": 1, "fo\\u006ft": 2}', errors: [ { messageId: "duplicateKey", - data: { key: "foot" }, + data: { key: "fo\\u006ft" }, line: 1, column: 18, endLine: 1, @@ -170,7 +225,7 @@ ruleTester.run("no-duplicate-keys", rule, { errors: [ { messageId: "duplicateKey", - data: { key: "foot" }, + data: { key: "fo\\u006ft" }, line: 1, column: 18, endLine: 1, @@ -184,7 +239,7 @@ ruleTester.run("no-duplicate-keys", rule, { errors: [ { messageId: "duplicateKey", - data: { key: "foot" }, + data: { key: "fo\\u006ft" }, line: 1, column: 18, endLine: 1, @@ -198,7 +253,7 @@ ruleTester.run("no-duplicate-keys", rule, { errors: [ { messageId: "duplicateKey", - data: { key: "foot" }, + data: { key: "fo\\u006ft" }, line: 1, column: 16, endLine: 1, From ce4f4df11fd9d6c6389cc40d6bfac759a8ad92d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Wed, 5 Nov 2025 23:38:28 +0900 Subject: [PATCH 2/8] wip --- src/util.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/util.js diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..f87a26a --- /dev/null +++ b/src/util.js @@ -0,0 +1,38 @@ +/** + * @fileoverview Utility Library + * @author 루밀LuMir(lumirlumir) + */ + +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** + * @import { MemberNode } from "@humanwhocodes/momoa"; + * @import { JSONSourceCode } from "./languages/json-source-code.js"; + */ + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +/** + * Gets the `MemberNode`'s key value. + * @param {MemberNode} node The node to get the key from. + * @returns {string} The key value. + */ +export function getKey(node) { + return node.name.type === "String" ? node.name.value : node.name.name; +} + +/** + * Gets the `MemberNode`'s raw key value. + * @param {MemberNode} node The node to get the raw key from. + * @param {JSONSourceCode} sourceCode The JSON source code object. + * @returns {string} The raw key value. + */ +export function getRawKey(node, sourceCode) { + return node.name.type === "String" + ? sourceCode.getText(node.name, -1, -1) + : sourceCode.getText(node.name); +} From a4d6a0025202e5157c20d6095acb97dda7a304f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Wed, 5 Nov 2025 23:42:24 +0900 Subject: [PATCH 3/8] wip --- src/rules/no-duplicate-keys.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/rules/no-duplicate-keys.js b/src/rules/no-duplicate-keys.js index 786e07c..73ede6d 100644 --- a/src/rules/no-duplicate-keys.js +++ b/src/rules/no-duplicate-keys.js @@ -3,6 +3,12 @@ * @author Nicholas C. Zakas */ +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { getKey, getRawKey } from "../util.js"; + //----------------------------------------------------------------------------- // Type Definitions //----------------------------------------------------------------------------- @@ -49,14 +55,8 @@ const rule = { }, Member(node) { - const key = - node.name.type === "String" - ? node.name.value - : node.name.name; - const rawKey = - node.name.type === "String" - ? context.sourceCode.getText(node.name, -1, -1) - : context.sourceCode.getText(node.name); + const key = getKey(node); + const rawKey = getRawKey(node, context.sourceCode); if (keys.has(key)) { context.report({ From 62e6924d8415f15b76631ba5440994f090bffe5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Wed, 5 Nov 2025 23:45:40 +0900 Subject: [PATCH 4/8] wip --- src/rules/no-empty-keys.js | 11 +++++++---- src/rules/no-unnormalized-keys.js | 10 ++++++---- src/rules/sort-keys.js | 12 +----------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/rules/no-empty-keys.js b/src/rules/no-empty-keys.js index 78fcd66..067d406 100644 --- a/src/rules/no-empty-keys.js +++ b/src/rules/no-empty-keys.js @@ -3,6 +3,12 @@ * @author Nicholas C. Zakas */ +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { getKey } from "../util.js"; + //----------------------------------------------------------------------------- // Type Definitions //----------------------------------------------------------------------------- @@ -37,10 +43,7 @@ const rule = { create(context) { return { Member(node) { - const key = - node.name.type === "String" - ? node.name.value - : node.name.name; + const key = getKey(node); if (key.trim() === "") { context.report({ diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index 8794cff..546cff4 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -2,6 +2,11 @@ * @fileoverview Rule to detect unnormalized keys in JSON. * @author Bradley Meck Farias */ +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { getKey } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -58,10 +63,7 @@ const rule = { return { Member(node) { - const key = - node.name.type === "String" - ? node.name.value - : node.name.name; + const key = getKey(node); if (key.normalize(form) !== key) { context.report({ diff --git a/src/rules/sort-keys.js b/src/rules/sort-keys.js index 2c6bc28..adeddd2 100644 --- a/src/rules/sort-keys.js +++ b/src/rules/sort-keys.js @@ -9,6 +9,7 @@ //----------------------------------------------------------------------------- import naturalCompare from "natural-compare"; +import { getKey } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -76,17 +77,6 @@ const comparators = { }, }; -/** - * Gets the MemberNode's string key value. - * @param {MemberNode} member - * @return {string} - */ -function getKey(member) { - return member.name.type === "Identifier" - ? member.name.name - : member.name.value; -} - //----------------------------------------------------------------------------- // Rule Definition //----------------------------------------------------------------------------- From 3b5c339cf6fef18ce251912725ceaab3ef74c0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Thu, 6 Nov 2025 00:00:56 +0900 Subject: [PATCH 5/8] wip --- tests/util.test.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/util.test.js diff --git a/tests/util.test.js b/tests/util.test.js new file mode 100644 index 0000000..c0b3ce9 --- /dev/null +++ b/tests/util.test.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Tests for util.js + * @author 루밀LuMir(lumirlumir) + */ + +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import assert from "node:assert"; +// import { JSONLanguage } from "../../src/languages/json-language.js"; +// import { JSONSourceCode } from "../../src/languages/json-source-code.js"; +import { + getKey, + // getRawKey +} from "../src/util.js"; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("util", () => { + describe("getKey()", () => { + it("should return the correct key for `String` nodes", () => { + const node = { + name: { type: "String", value: "value" }, + }; + + assert.strictEqual(getKey(node), "value"); + }); + + it("should return the correct key for `Identifier` nodes", () => { + const node = { + name: { type: "Identifier", name: "name" }, + }; + + assert.strictEqual(getKey(node), "name"); + }); + }); + + /* + describe("getRawKey()", () => { + const file = { body: `{"foo": 1, 'bar': 2, baz: 3}` }; + const language = new JSONLanguage({ mode: "jsonc" }); + const parseResult = language.parse(file); + const sourceCode = new JSONSourceCode({ + text: file.body, + ast: parseResult.ast, + }); + + it("TODO", () => { + // TODO + }); + }); + */ +}); From ab20452c87f2c217aa986d7a0062dbee0cfd9775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Thu, 6 Nov 2025 18:01:25 +0900 Subject: [PATCH 6/8] wip --- src/rules/no-unnormalized-keys.js | 1 + src/rules/sort-keys.js | 19 ++++++++++++------- tests/rules/sort-keys.test.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index 546cff4..2bb7b0c 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -2,6 +2,7 @@ * @fileoverview Rule to detect unnormalized keys in JSON. * @author Bradley Meck Farias */ + //----------------------------------------------------------------------------- // Imports //----------------------------------------------------------------------------- diff --git a/src/rules/sort-keys.js b/src/rules/sort-keys.js index adeddd2..5384bb8 100644 --- a/src/rules/sort-keys.js +++ b/src/rules/sort-keys.js @@ -9,7 +9,7 @@ //----------------------------------------------------------------------------- import naturalCompare from "natural-compare"; -import { getKey } from "../util.js"; +import { getKey, getRawKey } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -134,6 +134,7 @@ const rule = { }, create(context) { + const { sourceCode } = context; const [ directionShort, { allowLineSeparatedGroups, caseSensitive, natural, minKeys }, @@ -146,7 +147,7 @@ const rule = { // Note that @humanwhocodes/momoa doesn't include comments in the object.members tree, so we can't just see if a member is preceded by a comment const commentLineNums = new Set(); - for (const comment of context.sourceCode.comments) { + for (const comment of sourceCode.comments) { for ( let lineNum = comment.loc.start.line; lineNum <= comment.loc.end.line; @@ -177,9 +178,7 @@ const rule = { ) { if ( !commentLineNums.has(lineNum) && - !hasNonWhitespace.test( - context.sourceCode.lines[lineNum - 1], - ) + !hasNonWhitespace.test(sourceCode.lines[lineNum - 1]) ) { return true; } @@ -190,8 +189,12 @@ const rule = { return { Object(node) { + /** @type {MemberNode} */ let prevMember; + /** @type {string} */ let prevName; + /** @type {string} */ + let prevRawName; if (node.members.length < minKeys) { return; @@ -199,6 +202,7 @@ const rule = { for (const member of node.members) { const thisName = getKey(member); + const thisRawName = getRawKey(member, sourceCode); if ( prevMember && @@ -210,8 +214,8 @@ const rule = { loc: member.name.loc, messageId: "sortKeys", data: { - thisName, - prevName, + thisName: thisRawName, + prevName: prevRawName, direction, sensitivity, sortName, @@ -221,6 +225,7 @@ const rule = { prevMember = member; prevName = thisName; + prevRawName = thisRawName; } }, }; diff --git a/tests/rules/sort-keys.test.js b/tests/rules/sort-keys.test.js index c15bb15..bcda448 100644 --- a/tests/rules/sort-keys.test.js +++ b/tests/rules/sort-keys.test.js @@ -2029,5 +2029,22 @@ ruleTester.run("sort-keys", rule, { }, ], }, + + // Escape sequences in keys + { + code: '{"\\u0061":1, "\\u0063":2, "\\u0062":3}', + errors: [ + { + messageId: "sortKeys", + data: { + sortName: "alphanumeric", + sensitivity: "sensitive", + direction: "ascending", + thisName: "\\u0062", + prevName: "\\u0063", + }, + }, + ], + }, ], }); From 49f5b73753e247d437e9faba79797b9908db6dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Thu, 6 Nov 2025 20:38:13 +0900 Subject: [PATCH 7/8] wip --- src/rules/sort-keys.js | 2 +- tests/rules/sort-keys.test.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rules/sort-keys.js b/src/rules/sort-keys.js index 5384bb8..20dc797 100644 --- a/src/rules/sort-keys.js +++ b/src/rules/sort-keys.js @@ -104,7 +104,7 @@ const rule = { messages: { sortKeys: - "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.", // TODO + "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.", }, schema: [ diff --git a/tests/rules/sort-keys.test.js b/tests/rules/sort-keys.test.js index bcda448..7c58512 100644 --- a/tests/rules/sort-keys.test.js +++ b/tests/rules/sort-keys.test.js @@ -2046,5 +2046,20 @@ ruleTester.run("sort-keys", rule, { }, ], }, + { + code: '{"\\u0061\\n":1, "\\u0063\\n":2, "\\u0062\\n":3}', + errors: [ + { + messageId: "sortKeys", + data: { + sortName: "alphanumeric", + sensitivity: "sensitive", + direction: "ascending", + thisName: "\\u0062\\n", + prevName: "\\u0063\\n", + }, + }, + ], + }, ], }); From b9e21bcb079bb02f9064eaa282a02a9256568337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Thu, 6 Nov 2025 20:50:20 +0900 Subject: [PATCH 8/8] wip --- tests/rules/sort-keys.test.js | 8 ++++++++ tests/util.test.js | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/tests/rules/sort-keys.test.js b/tests/rules/sort-keys.test.js index 7c58512..f50f510 100644 --- a/tests/rules/sort-keys.test.js +++ b/tests/rules/sort-keys.test.js @@ -2043,6 +2043,10 @@ ruleTester.run("sort-keys", rule, { thisName: "\\u0062", prevName: "\\u0063", }, + line: 1, + column: 26, + endLine: 1, + endColumn: 34, }, ], }, @@ -2058,6 +2062,10 @@ ruleTester.run("sort-keys", rule, { thisName: "\\u0062\\n", prevName: "\\u0063\\n", }, + line: 1, + column: 30, + endLine: 1, + endColumn: 40, }, ], }, diff --git a/tests/util.test.js b/tests/util.test.js index c0b3ce9..2e68f78 100644 --- a/tests/util.test.js +++ b/tests/util.test.js @@ -8,12 +8,9 @@ //------------------------------------------------------------------------------ import assert from "node:assert"; -// import { JSONLanguage } from "../../src/languages/json-language.js"; -// import { JSONSourceCode } from "../../src/languages/json-source-code.js"; -import { - getKey, - // getRawKey -} from "../src/util.js"; +import { JSONLanguage } from "../src/languages/json-language.js"; +import { JSONSourceCode } from "../src/languages/json-source-code.js"; +import { getKey, getRawKey } from "../src/util.js"; //------------------------------------------------------------------------------ // Tests @@ -38,19 +35,31 @@ describe("util", () => { }); }); - /* describe("getRawKey()", () => { const file = { body: `{"foo": 1, 'bar': 2, baz: 3}` }; - const language = new JSONLanguage({ mode: "jsonc" }); + const language = new JSONLanguage({ mode: "json5" }); const parseResult = language.parse(file); const sourceCode = new JSONSourceCode({ text: file.body, ast: parseResult.ast, }); - it("TODO", () => { - // TODO + it("should return correct raw key for `String` nodes with double quotes", () => { + const fooNode = parseResult.ast.body.members[0]; + + assert.strictEqual(getRawKey(fooNode, sourceCode), "foo"); + }); + + it("should return correct raw key for `String` nodes with single quotes", () => { + const barNode = parseResult.ast.body.members[1]; + + assert.strictEqual(getRawKey(barNode, sourceCode), "bar"); + }); + + it("should return correct raw key for `Identifier` nodes", () => { + const bazNode = parseResult.ast.body.members[2]; + + assert.strictEqual(getRawKey(bazNode, sourceCode), "baz"); }); }); - */ });