Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-cjk-false-positive.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => <p className="text-sm">一部のフォルダにアクセスできないため、移動対象を検出できていない可能性があります。フォルダのアクセス権を確認してから更新してください。</p>;`,
);
expect(result.diagnostics).toHaveLength(0);
});

it("does not flag long Chinese text (caseless script)", () => {
const result = runRule(
noAllCapsBodyText,
`const Example = () => <p>由于无法访问某些文件夹,可能无法检测到移动目标。请检查文件夹的访问权限,然后更新。</p>;`,
);
expect(result.diagnostics).toHaveLength(0);
});

it("does not flag long Korean text (caseless script)", () => {
const result = runRule(
noAllCapsBodyText,
`const Example = () => <p>일부 폴더에 액세스할 수 없으므로 이동 대상을 감지할 수 없습니다. 폴더 액세스 권한을 확인한 후 업데이트하십시오.</p>;`,
);
expect(result.diagnostics).toHaveLength(0);
});

it("does not flag long Arabic text (caseless script)", () => {
const result = runRule(
noAllCapsBodyText,
`const Example = () => <p>لا يمكن الوصول إلى بعض المجلدات، لذا قد لا يتم اكتشاف أهداف النقل. يرجى التحقق من أذونات الوصول إلى المجلد، ثم التحديث.</p>;`,
);
expect(result.diagnostics).toHaveLength(0);
});

it("does not flag mixed script text (English + Japanese) without uppercase", () => {
const result = runRule(
noAllCapsBodyText,
`const Example = () => <p>Please check フォルダのアクセス権を確認してから更新してください your folder permissions before continuing.</p>;`,
);
expect(result.diagnostics).toHaveLength(0);
});

it("flags mixed script text when English portion is all caps", () => {
const result = runRule(
noAllCapsBodyText,
`const Example = () => <p>PLEASE CHECK フォルダのアクセス権を確認してから更新してください YOUR FOLDER PERMISSIONS.</p>;`,
);
expect(result.diagnostics).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
Expand Down
Loading