diff --git a/.changeset/fix-cjk-false-positive.md b/.changeset/fix-cjk-false-positive.md new file mode 100644 index 000000000..98e69e14a --- /dev/null +++ b/.changeset/fix-cjk-false-positive.md @@ -0,0 +1,9 @@ +--- +"oxlint-plugin-react-doctor": patch +--- + +Fix `no-all-caps-body-text` false positive on CJK text (caseless scripts) + +The rule was incorrectly flagging Japanese, Chinese, Korean, and Arabic text as "all caps" because it treated any text without lowercase letters as uppercase. Caseless scripts have no letter case, so they were always flagged. + +Now the rule only flags text that actually contains uppercase letters AND lacks lowercase letters. Caseless scripts are skipped entirely. diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.test.ts index fbbdd0f0b..bc87344df 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.test.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.test.ts @@ -50,4 +50,52 @@ describe("no-all-caps-body-text", () => { ); expect(result.diagnostics).toHaveLength(0); }); + + it("does not flag long Japanese text (caseless script)", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

一部のフォルダにアクセスできないため、移動対象を検出できていない可能性があります。フォルダのアクセス権を確認してから更新してください。

;`, + ); + expect(result.diagnostics).toHaveLength(0); + }); + + it("does not flag long Chinese text (caseless script)", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

由于无法访问某些文件夹,可能无法检测到移动目标。请检查文件夹的访问权限,然后更新。

;`, + ); + expect(result.diagnostics).toHaveLength(0); + }); + + it("does not flag long Korean text (caseless script)", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

일부 폴더에 액세스할 수 없으므로 이동 대상을 감지할 수 없습니다. 폴더 액세스 권한을 확인한 후 업데이트하십시오.

;`, + ); + expect(result.diagnostics).toHaveLength(0); + }); + + it("does not flag long Arabic text (caseless script)", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

لا يمكن الوصول إلى بعض المجلدات، لذا قد لا يتم اكتشاف أهداف النقل. يرجى التحقق من أذونات الوصول إلى المجلد، ثم التحديث.

;`, + ); + expect(result.diagnostics).toHaveLength(0); + }); + + it("does not flag mixed script text (English + Japanese) without uppercase", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

Please check フォルダのアクセス権を確認してから更新してください your folder permissions before continuing.

;`, + ); + expect(result.diagnostics).toHaveLength(0); + }); + + it("flags mixed script text when English portion is all caps", () => { + const result = runRule( + noAllCapsBodyText, + `const Example = () =>

PLEASE CHECK フォルダのアクセス権を確認してから更新してください YOUR FOLDER PERMISSIONS.

;`, + ); + expect(result.diagnostics).toHaveLength(1); + }); }); diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.ts index 39384adb4..5e6764b12 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/design/no-all-caps-body-text.ts @@ -12,6 +12,7 @@ import { getStylePropertyStringValue } from "./utils/get-style-property-string-v const BODY_TEXT_ELEMENT_NAMES = new Set(["blockquote", "dd", "figcaption", "li", "p", "td"]); const LETTER_PATTERN = /\p{L}/u; +const UPPERCASE_LETTER_PATTERN = /\p{Lu}/u; const LOWERCASE_LETTER_PATTERN = /\p{Ll}/u; const hasUppercaseStyle = (node: EsTreeNodeOfType<"JSXOpeningElement">): boolean => { @@ -47,7 +48,9 @@ export const noAllCapsBodyText = defineRule({ if (staticText.length < LONG_BODY_TEXT_MIN_CHARACTERS || !LETTER_PATTERN.test(staticText)) { return; } - const isLiteralUppercase = !LOWERCASE_LETTER_PATTERN.test(staticText); + const hasUppercase = UPPERCASE_LETTER_PATTERN.test(staticText); + const hasLowercase = LOWERCASE_LETTER_PATTERN.test(staticText); + const isLiteralUppercase = hasUppercase && !hasLowercase; if (!isLiteralUppercase && !hasUppercaseStyle(openingElement)) return; context.report({ node: openingElement,