Skip to content

Commit 178ea0d

Browse files
committed
fix(no-unlocalized-strings): support unions with primitives
String literal unions can include other primitive types: - undefined, null - number literals (e.g., 'a' | 'b' | 1 | 2) - boolean literals (e.g., 'enabled' | true | false) Only ignore if the union contains at least one string literal.
1 parent 0a846ac commit 178ea0d

File tree

2 files changed

+51
-5
lines changed

2 files changed

+51
-5
lines changed

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ ruleTester.run("no-unlocalized-strings", noUnlocalizedStrings, {
111111
setMode("dark")
112112
`,
113113
filename: "test.tsx"
114+
},
115+
116+
// TypeScript: String literal unions with undefined/null
117+
{
118+
code: `
119+
type OptionalStatus = "loading" | "error" | undefined
120+
const status: OptionalStatus = "loading"
121+
`,
122+
filename: "test.tsx"
123+
},
124+
{
125+
code: `
126+
type NullableMode = "dark" | "light" | null
127+
const mode: NullableMode = "dark"
128+
`,
129+
filename: "test.tsx"
130+
},
131+
{
132+
code: `
133+
function setVariant(v: "primary" | "secondary" | undefined) {}
134+
setVariant("primary")
135+
`,
136+
filename: "test.tsx"
114137
}
115138
],
116139
invalid: [

src/rules/no-unlocalized-strings.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,35 @@ function isTechnicalStringType(
259259
const contextualType = typeChecker.getContextualType(tsNode)
260260

261261
if (contextualType !== undefined) {
262-
// Check if contextual type is a union of string literals
262+
// Check if contextual type is a union containing string literals
263+
// Allow other primitive types: undefined, null, boolean, number
263264
if (contextualType.isUnion()) {
264-
const allStringLiterals = contextualType.types.every(
265-
(t) => t.isStringLiteral() || (t.flags & 128) !== 0 // StringLiteral flag
266-
)
267-
if (allStringLiterals) {
265+
const hasStringLiteral = contextualType.types.some((t) => t.isStringLiteral() || (t.flags & 128) !== 0)
266+
const allTechnical = contextualType.types.every((t) => {
267+
// String literal (flags: 128)
268+
if (t.isStringLiteral() || (t.flags & 128) !== 0) {
269+
return true
270+
}
271+
// Number literal (flags: 256)
272+
if (t.isNumberLiteral() || (t.flags & 256) !== 0) {
273+
return true
274+
}
275+
// Boolean literal - true (flags: 512) or false (flags: 1024)
276+
if ((t.flags & 512) !== 0 || (t.flags & 1024) !== 0) {
277+
return true
278+
}
279+
// undefined (flags: 32768)
280+
if ((t.flags & 32768) !== 0) {
281+
return true
282+
}
283+
// null (flags: 65536)
284+
if ((t.flags & 65536) !== 0) {
285+
return true
286+
}
287+
return false
288+
})
289+
// Only ignore if the union contains at least one string literal
290+
if (hasStringLiteral && allTechnical) {
268291
return true
269292
}
270293
}

0 commit comments

Comments
 (0)