Skip to content

fix: escape normalized keys in no-unnormalized-keys autofix - #283

Open
electrohyun wants to merge 3 commits into
eslint:mainfrom
electrohyun:fix/no-unnormalized-keys-escaping
Open

fix: escape normalized keys in no-unnormalized-keys autofix#283
electrohyun wants to merge 3 commits into
eslint:mainfrom
electrohyun:fix/no-unnormalized-keys-escaping

Conversation

@electrohyun

@electrohyun electrohyun commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Prerequisites checklist

AI acknowledgment

  • I did not use AI to generate this PR.
  • (If the above is not checked) I have reviewed the AI-generated content before submitting.

What is the purpose of this pull request?

Fix no-unnormalized-keys autofix producing invalid JSON when normalization introduces quotes or \.

What changes did you make? (Give an overview)

Escape \ and matching quotes in normalized keys.

Add regression tests for NFKC and NFKD normalization across JSON, JSONC, and JSON5 (single quote cases are included only in JSON5).

Related Issues

fixes #282

Is there anything you'd like reviewers to focus on?

At first, I wrote code with a let variable, but it ended up using that variable’s name instead of normalizedKey, and I think this is less consistent with the rule name no-unnormalized-keys.

The current approach does the same by using a different name, escapedKey, but only for string keys and normalizedKey is still used for identifier keys, so I went with this structure.

If there’s a better way to structure the fixer, please let me know.


Disclosure: I'm a participant of open source contribution program OSSCA

Summary by CodeRabbit

  • Refactor

    • Improved the handling of escaped characters when automatically fixing unnormalized string keys.
    • Preserved existing behavior for normalized keys and supported JSON formats.
  • Tests

    • Added coverage for full-width quotation marks, apostrophes, and backslashes under NFKC and NFKD normalization.
    • Expanded validation across JSON, JSONC, and JSON5, including single-quoted keys.

@eslint-github-bot eslint-github-bot Bot added the bug Something isn't working label Sep 8, 2026
@eslintbot eslintbot added this to Triage Sep 8, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in Triage Sep 8, 2026
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 3e14e76e-3464-4477-85bd-0ccafe7fa937

📥 Commits

Reviewing files that changed from the base of the PR and between d1dd869 and 90d3342.

📒 Files selected for processing (2)
  • src/rules/no-unnormalized-keys.js
  • tests/rules/no-unnormalized-keys.test.js

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The no-unnormalized-keys autofix now escapes backslashes and quote characters in normalized string keys. Tests cover JSON, JSONC, and JSON5 under NFKC and NFKD normalization.

Changes

Normalized key autofix

Layer / File(s) Summary
Escape normalized string keys and validate fixes
src/rules/no-unnormalized-keys.js, tests/rules/no-unnormalized-keys.test.js
String-key replacements now escape backslashes and matching quote characters. Tests cover full-width quotes, apostrophes, and backslashes across JSON, JSONC, and JSON5 for NFKC and NFKD.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 90d33

Normalized string-key autofixes now preserve valid JSON-family syntax when normalization produces quotes or backslashes. Coverage includes the affected formats and normalization forms, with no current merge-blocking risk identified.

Suggested reviewers: lumirlumir

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: escaping normalized keys in the no-unnormalized-keys autofix.
Linked Issues check ✅ Passed The implementation and regression tests address issue [#282] by escaping normalized string keys so quotes and backslashes produce valid JSON, JSONC, and JSON5 output.
Out of Scope Changes check ✅ Passed The changes are limited to the normalized-key escaping helper and regression tests for the reported autofix defect.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

},
{
code: `{"a"b\c'"\": 1}`,
output: `{"a\\"b\\\\c'\\"\\\\": 1}`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use String.raw for readability here?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that using String.raw for every output property would be more readable.

@DMartens DMartens left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR.
The logic looks good to me, just one suggested refactoring and additional test cases.

Comment thread src/rules/no-unnormalized-keys.js Outdated
: name.range,
normalizedKey,
);
if (name.type === "String") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could simplify this as only the "key" is changed:

const fixedKey = name.type === "String" ? escapeKey(...) : normalizedKey;
return fixer.replaceText(name, fixedKey);

and extract the quote + replaceAll logic into a function (which now adds the same quotes around the key as the whole key is replaced).
As you mentioned it is hard to come up with a better name as normalizedKey is in scope.

],
},
{
code: `{"a"b\c'"\": 1}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add separate test cases for one options.form for the quotes and escaping logic (e.g. { "a'b": 1 } showing that the not used quote is not escaped).
You can keep the "combined" test cases.

@DMartens DMartens moved this from Needs Triage to Implementing in Triage Sep 8, 2026
@electrohyun

Copy link
Copy Markdown
Contributor Author

@DMartens Applied. I think fixedKey looks good. Thank you for the review!

],
},
{
code: `{"a"b": 1}`,

@lumirlumir lumirlumir Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
code: `{"a"b": 1}`,
code: `{"a\uff02b": 1}`,

Disclosure: I'm a participant of open source contribution program OSSCA: confirmed.

If using U+FF02 is the intention of this test, could we use the Unicode escape sequence instead? In some code editors or IDEs, it may look similar to U+0022, which could confuse people when reading the test cases.

It would also be helpful to update the other test cases affected by this.

);
? escapeKey(
normalizedKey,
context.sourceCode.getText(name),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context.sourceCode.getText(name),
sourceCode.getText(name),
+ const { sourceCode } = context;
const [{ form }] = context.options;

Small suggestion: I think we could destructure sourceCode at the top to avoid the minor overhead of accessing it each time, since there’s one more occurrence of context.sourceCode.

);
? escapeKey(
normalizedKey,
context.sourceCode.getText(name),

@lumirlumir lumirlumir Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we need to call the getText method here. We can simply access the quote information as follows:

							const fixedKey =
								name.type === "String"
									? escapeKey(
											normalizedKey,
											context.sourceCode.text[
												name.range[0]
											],
										)
									: normalizedKey;

The helper function could then be simplified as follows:

/**
 * Escapes a normalized string key and wraps it in its original quotes.
 * @param {string} normalizedKey The normalized key to escape.
 * @param {string} quote The quote character used in the original key.
 * @returns {string} The escaped and quoted key.
 */
function escapeKey(normalizedKey, quote) {
	const escapedKey = normalizedKey
		.replaceAll("\\", "\\\\")
		.replaceAll(quote, `\\${quote}`);

	return `${quote}${escapedKey}${quote}`;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accepted bug Something isn't working

Projects

Status: Implementing

Development

Successfully merging this pull request may close these issues.

Bug: no-unnormalized-keys autofix normalizes key without escaping " or \

4 participants