Skip to content

Commit 8a88bf8

Browse files
committed
feat(no-unlocalized-strings): auto-ignore camelCase styling properties
- ClassName, Class, Color, Style, Icon, Size, Id suffixes - Requires valid camelCase with prefix (e.g. containerClassName, backgroundColor)
1 parent f245aea commit 8a88bf8

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

src/rules/no-unlocalized-strings.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,34 @@ ruleTester.run("no-unlocalized-strings", noUnlocalizedStrings, {
8585
{ code: '({ type: "button" })', filename: "test.tsx" },
8686
{ code: '({ className: "my-class" })', filename: "test.tsx" },
8787

88+
// CSS class name properties (camelCase ending with Class or ClassName)
89+
{ code: '<Button containerClassName="flex items-center" />', filename: "test.tsx" },
90+
{ code: '<Input wrapperClassName="mt-4 mb-2" />', filename: "test.tsx" },
91+
{ code: '<Card headerClass="text-lg font-bold" />', filename: "test.tsx" },
92+
{ code: '<Modal overlayClass="bg-black opacity-50" />', filename: "test.tsx" },
93+
{ code: '({ buttonClassName: "px-4 py-2" })', filename: "test.tsx" },
94+
{ code: '({ inputClass: "border rounded" })', filename: "test.tsx" },
95+
// Complex camelCase class name properties
96+
{ code: '<Select inputElementClassName="text-sm placeholder-gray-400" />', filename: "test.tsx" },
97+
{ code: '<DatePicker calendarPopoverClassName="shadow-lg rounded-xl" />', filename: "test.tsx" },
98+
// Color properties
99+
{ code: '<Box backgroundColor="#ff0000" />', filename: "test.tsx" },
100+
{ code: '<Text textColor="red-500" />', filename: "test.tsx" },
101+
{ code: '<Button borderColor="gray.200" />', filename: "test.tsx" },
102+
{ code: '({ accentColor: "blue" })', filename: "test.tsx" },
103+
// Style properties
104+
{ code: '<View containerStyle="flex-1" />', filename: "test.tsx" },
105+
{ code: '({ buttonStyle: "primary" })', filename: "test.tsx" },
106+
// Icon properties
107+
{ code: '<Button leftIcon="arrow-left" />', filename: "test.tsx" },
108+
{ code: '<Alert statusIcon="warning" />', filename: "test.tsx" },
109+
// Size properties
110+
{ code: '<Text fontSize="lg" />', filename: "test.tsx" },
111+
{ code: '<Avatar iconSize="24" />', filename: "test.tsx" },
112+
// Id properties
113+
{ code: '<Section containerId="main-section" />', filename: "test.tsx" },
114+
{ code: '({ elementId: "header" })', filename: "test.tsx" },
115+
88116
// Technical strings (no spaces, identifiers)
89117
{ code: 'const x = "myIdentifier"', filename: "test.tsx" },
90118
{ code: 'const x = "my-css-class"', filename: "test.tsx" },

src/rules/no-unlocalized-strings.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,30 @@ function isErrorConstructorArgument(
480480
return false
481481
}
482482

483+
/** Valid camelCase: lowercase start, then (Uppercase + lowercase+) sequences */
484+
const CAMEL_CASE_PATTERN = /^[a-z]+([A-Z][a-z]+)+$/
485+
486+
/** Technical suffixes with at least one lowercase char before (ensures prefix exists) */
487+
const STYLING_SUFFIX_PATTERN = /[a-z](ClassName|Class|Color|Style|Icon|Size|Id)$/
488+
489+
/**
490+
* Checks if a property name is a styling/technical property.
491+
*
492+
* Matches camelCase properties ending with:
493+
* - "Class" or "ClassName": containerClassName, buttonClass
494+
* - "Color": backgroundColor, borderColor, textColor
495+
* - "Style": containerStyle, buttonStyle
496+
* - "Icon": leftIcon, statusIcon
497+
* - "Size": fontSize, iconSize
498+
* - "Id": containerId, elementId
499+
*
500+
* This covers common patterns for component libraries that accept
501+
* styling/technical props which contain technical values, not user text.
502+
*/
503+
function isStylingProperty(propertyName: string): boolean {
504+
return CAMEL_CASE_PATTERN.test(propertyName) && STYLING_SUFFIX_PATTERN.test(propertyName)
505+
}
506+
483507
/**
484508
* Checks if a string is a value for an ignored property/attribute.
485509
*/
@@ -489,17 +513,26 @@ function isIgnoredProperty(node: TSESTree.Node, ignoreProperties: string[]): boo
489513
// JSX attribute: <div className="..." />
490514
if (parent?.type === AST_NODE_TYPES.JSXAttribute) {
491515
if (parent.name.type === AST_NODE_TYPES.JSXIdentifier) {
492-
return ignoreProperties.includes(parent.name.name)
516+
const name = parent.name.name
517+
if (ignoreProperties.includes(name) || isStylingProperty(name)) {
518+
return true
519+
}
493520
}
494521
}
495522

496523
// Object property: { className: "..." }
497524
if (parent?.type === AST_NODE_TYPES.Property) {
498525
if (parent.key.type === AST_NODE_TYPES.Identifier) {
499-
return ignoreProperties.includes(parent.key.name)
526+
const name = parent.key.name
527+
if (ignoreProperties.includes(name) || isStylingProperty(name)) {
528+
return true
529+
}
500530
}
501531
if (parent.key.type === AST_NODE_TYPES.Literal && typeof parent.key.value === "string") {
502-
return ignoreProperties.includes(parent.key.value)
532+
const name = parent.key.value
533+
if (ignoreProperties.includes(name) || isStylingProperty(name)) {
534+
return true
535+
}
503536
}
504537
}
505538

0 commit comments

Comments
 (0)