Skip to content

Commit 43ce3fb

Browse files
committed
refactor(no-unlocalized-strings): type-based Console/Error detection, minimal defaults
DEFAULT_IGNORE_FUNCTIONS: require, import (Console/Error auto-detected via types) DEFAULT_IGNORE_PROPERTIES: className, key, data-testid (3 entries) Added type-aware detection: - isConsoleMethodArgument() - detects Console type - isErrorConstructorArgument() - detects Error type hierarchy
1 parent a1b174f commit 43ce3fb

File tree

3 files changed

+106
-13
lines changed

3 files changed

+106
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ The `no-unlocalized-strings` rule has different options because TypeScript types
144144
|-----------------|-------------|-------|
145145
| `useTsTypes` || Always enabled (TypeScript required) |
146146
| `ignore` (array of regex) | `ignorePattern` (single regex) | Simplified |
147-
| `ignoreFunctions` | `ignoreFunctions` |Compatible (wildcards work slightly differently) |
147+
| `ignoreFunctions` | `ignoreFunctions` |Simplified (Console/Error auto-detected) |
148148
| `ignoreNames` (with regex support) | `ignoreNames` | Simplified (no regex, plain strings only) |
149149
|| `ignoreProperties` | New: separate option for JSX attributes and object properties |
150150
| `ignoreMethodsOnTypes` || Not needed (TypeScript handles this automatically) |

docs/rules/no-unlocalized-strings.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,24 @@ const action = { type: "save" } // ✅ Not reported (type/kind properties)
108108

109109
Array of function names whose string arguments should be ignored. Supports wildcards.
110110

111-
Default: `["console.*", "require", "import", "Error", "TypeError", "RangeError", "SyntaxError"]`
111+
Default: `["require", "import"]`
112+
113+
Most common cases are detected automatically via TypeScript types:
114+
- **Console methods** (`console.log`, `console.error`, etc.) — detected via `Console` type
115+
- **Error constructors** (`new Error()`, `new TypeError()`, etc.) — detected via Error type hierarchy
112116

113117
```ts
114118
{
115119
"lingui-ts/no-unlocalized-strings": ["error", {
116-
"ignoreFunctions": ["console.*", "require", "logger.*", "analytics.track"]
120+
"ignoreFunctions": ["require", "import", "logger.*", "analytics.track"]
117121
}]
118122
}
119123
```
120124

121125
Wildcard examples:
122126

123-
- `"console.*"` matches `console.log`, `console.error`, etc.
124-
- `"*.debug"` matches `logger.debug`, `app.debug`, etc.
127+
- `"logger.*"` matches `logger.debug`, `logger.info`, etc.
128+
- `"*.track"` matches `analytics.track`, `events.track`, etc.
125129

126130
### `ignoreProperties`
127131

@@ -134,10 +138,8 @@ Default:
134138
```ts
135139
[
136140
"className", // CSS classes - arbitrary strings, always technical
137-
"styleName", // CSS modules
138141
"key", // React key prop
139-
"testID", // React Native testing
140-
"data-testid" // DOM testing
142+
"data-testid" // DOM Testing Library standard
141143
]
142144
```
143145

src/rules/no-unlocalized-strings.ts

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export interface Options {
1919
/**
2020
* Functions whose arguments should not be checked for localization.
2121
* Supports wildcards: "console.*" matches console.log, console.error, etc.
22+
*
23+
* This list is intentionally minimal - Error constructors and console methods
24+
* are detected automatically via TypeScript types.
2225
*/
23-
const DEFAULT_IGNORE_FUNCTIONS = ["console.*", "require", "import", "Error", "TypeError", "RangeError", "SyntaxError"]
26+
const DEFAULT_IGNORE_FUNCTIONS = ["require", "import"]
2427

2528
/**
2629
* JSX attributes and object properties whose values should not be checked.
@@ -35,11 +38,9 @@ const DEFAULT_IGNORE_FUNCTIONS = ["console.*", "require", "import", "Error", "Ty
3538
const DEFAULT_IGNORE_PROPERTIES = [
3639
// CSS class names - accept arbitrary strings, always technical
3740
"className",
38-
"styleName",
39-
// React-specific
41+
// React key prop
4042
"key",
41-
// Testing IDs - arbitrary strings, always technical
42-
"testID",
43+
// Testing ID - DOM Testing Library standard
4344
"data-testid"
4445
]
4546

@@ -297,6 +298,86 @@ function isIgnoredFunctionArgument(node: TSESTree.Node, ignoreFunctions: string[
297298
return false
298299
}
299300

301+
/**
302+
* Checks if a string is an argument to a Console method (console.log, etc.)
303+
* using TypeScript type information.
304+
*/
305+
function isConsoleMethodArgument(
306+
node: TSESTree.Node,
307+
typeChecker: ts.TypeChecker,
308+
parserServices: ReturnType<typeof ESLintUtils.getParserServices>
309+
): boolean {
310+
const parent = node.parent
311+
if (parent?.type !== AST_NODE_TYPES.CallExpression) {
312+
return false
313+
}
314+
315+
const callee = parent.callee
316+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
317+
return false
318+
}
319+
320+
try {
321+
const objectTsNode = parserServices.esTreeNodeToTSNodeMap.get(callee.object)
322+
const objectType = typeChecker.getTypeAtLocation(objectTsNode)
323+
const typeName = typeChecker.typeToString(objectType)
324+
325+
// Check if the object is of type Console
326+
return typeName === "Console"
327+
} catch {
328+
return false
329+
}
330+
}
331+
332+
/**
333+
* Checks if a string is an argument to an Error constructor
334+
* using TypeScript type information.
335+
*/
336+
function isErrorConstructorArgument(
337+
node: TSESTree.Node,
338+
typeChecker: ts.TypeChecker,
339+
parserServices: ReturnType<typeof ESLintUtils.getParserServices>
340+
): boolean {
341+
const parent = node.parent
342+
if (parent?.type !== AST_NODE_TYPES.NewExpression) {
343+
return false
344+
}
345+
346+
try {
347+
const calleeTsNode = parserServices.esTreeNodeToTSNodeMap.get(parent.callee)
348+
const calleeType = typeChecker.getTypeAtLocation(calleeTsNode)
349+
350+
// Check if the constructor creates an Error type
351+
const constructSignatures = calleeType.getConstructSignatures()
352+
for (const sig of constructSignatures) {
353+
const returnType = sig.getReturnType()
354+
const returnTypeName = typeChecker.typeToString(returnType)
355+
356+
// Check if it returns Error or any Error subtype
357+
if (returnTypeName === "Error" || returnTypeName.endsWith("Error")) {
358+
return true
359+
}
360+
361+
// Check if the return type extends Error
362+
if ("getBaseTypes" in returnType && typeof returnType.getBaseTypes === "function") {
363+
const baseTypes = returnType.getBaseTypes()
364+
if (baseTypes !== undefined) {
365+
for (const baseType of baseTypes) {
366+
const baseTypeName = typeChecker.typeToString(baseType)
367+
if (baseTypeName === "Error") {
368+
return true
369+
}
370+
}
371+
}
372+
}
373+
}
374+
} catch {
375+
return false
376+
}
377+
378+
return false
379+
}
380+
300381
/**
301382
* Checks if a string is a value for an ignored property/attribute.
302383
*/
@@ -636,6 +717,16 @@ export const noUnlocalizedStrings = createRule<[Options], MessageId>({
636717
return
637718
}
638719

720+
// Console method argument (type-aware)
721+
if (isConsoleMethodArgument(node, typeChecker, parserServices)) {
722+
return
723+
}
724+
725+
// Error constructor argument (type-aware)
726+
if (isErrorConstructorArgument(node, typeChecker, parserServices)) {
727+
return
728+
}
729+
639730
// Value for ignored property
640731
if (isIgnoredProperty(node, options.ignoreProperties)) {
641732
return

0 commit comments

Comments
 (0)