Skip to content

Commit

Permalink
feat(no-unlocalized-strings): add regex patterns support (#70)
Browse files Browse the repository at this point in the history
* feat(no-unlocalized-strings): add regex patterns for ignoreAttribute, strictAttribute, ignoreProperty

* feat(no-unlocalized-strings): improve the readme
  • Loading branch information
timofei-iatsenko authored Oct 18, 2024
1 parent b048305 commit 1c248a2
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 163 deletions.
181 changes: 140 additions & 41 deletions docs/rules/no-unlocalized-strings.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
# no-unlocalized-strings

Check that code doesn't contain strings/templates/jsxText what should be wrapped into `<Trans>` or `i18n`
Ensures that all string literals, templates, and JSX text are wrapped using `<Trans>`, `t`, or `msg` for localization.

> [!IMPORTANT]
> This rule might use type information. You can enable it with `{useTsTypes: true}`
> This rule may require TypeScript type information. Enable this feature by setting `{ useTsTypes: true }`.
## Options

### useTsTypes
### `useTsTypes`

Use additional TypeScript type information. Requires [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) to be setup.
Enables the rule to use TypeScript type information. Requires [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) to be configured.

Will automatically exclude some built-in methods such as `Map` and `Set`, and also cases where a string literal is used as a TypeScript constant:
This option automatically excludes built-in methods such as `Map` and `Set`, and cases where string literals are used as TypeScript constants, e.g.:

```ts
const a: 'abc' = 'abc'
```

### ignore
### `ignore`

The `ignore` option specifies exceptions not to check for
literal strings that match one of regexp patterns.
Specifies patterns for string literals to ignore. Strings matching any of the provided regular expressions will not trigger the rule.

Examples of correct code for the `{ "ignore": ["rgba"] }` option:
Example for `{ "ignore": ["rgba"] }`:

```jsx
/*eslint lingui/no-unlocalized-strings ["error", {"ignore": ["rgba"]}]*/
const a = <div color="rgba(100, 100, 100, 0.4)"></div>
/*eslint lingui/no-unlocalized-strings: ["error", {"ignore": ["rgba"]}]*/
const color = <div style={{ color: 'rgba(100, 100, 100, 0.4)' }} />
```

### ignoreFunction
### `ignoreFunction`

The `ignoreFunction` option specifies exceptions not check for
function calls whose names match one of regexp patterns.
Specifies functions whose string arguments should be ignored.

Examples of correct code for the `{ "ignoreFunction": ["showIntercomMessage"] }` option:
Example of `correct` code with this option:

```js
/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunction": ["showIntercomMessage"] }]*/
const bar = showIntercomMessage('Please, write me')
/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["showIntercomMessage"]}]*/
showIntercomMessage('Please write me')

/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunction": ["cva"] }]*/
const labelVariants = cva('text-form-input-content-helper', {
Expand All @@ -51,62 +49,163 @@ const labelVariants = cva('text-form-input-content-helper', {
})
```

### ignoreAttribute
This option also supports member expressions. Example for `{ "ignoreFunction": ["console.log"] }`:

The `ignoreAttribute` option specifies exceptions not to check for JSX attributes that match one of ignored attributes.
```js
/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["console.log"]}]*/
console.log('Log this message')
```

> **Note:** Only single-level patterns are supported. For instance, `foo.bar.baz` will not be matched.
### `ignoreAttribute`

Examples of correct code for the `{ "ignoreAttribute": ["style"] }` option:
Specifies JSX attributes that should be ignored. By default, the attributes `className`, `styleName`, `type`, `id`, `width`, and `height` are ignored.

Example for `{ "ignoreAttribute": ["style"] }`:

```jsx
/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreAttribute": ["style"] }]*/
/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreAttribute": ["style"]}]*/
const element = <div style={{ margin: '1rem 2rem' }} />
```

By default, the following attributes are ignored: `className`, `styleName`, `type`, `id`, `width`, `height`
#### `regex`

Defines regex patterns for ignored attributes.

Example:

```json
{
"no-unlocalized-strings": [
"error",
{
"ignoreAttribute": [
{
"regex": {
"pattern": "classname",
"flags": "i"
}
}
]
}
]
}
```

### strictAttribute
Example of **correct** code:

The `strictAttribute` option specifies JSX attributes which will always be checked regardless of `ignore`
option or any built-in exceptions.
```jsx
const element = <div wrapperClassName="absolute top-1/2 left-1/2" />
```

### `strictAttribute`

Examples of incorrect code for the `{ "strictAttribute": ["alt"] }` option:
Specifies JSX attributes that should always be checked, regardless of other `ignore` settings or defaults.

Example for `{ "strictAttribute": ["alt"] }`:

```jsx
/*eslint lingui/no-unlocalized-strings: ["error", { "strictAttribute": ["alt"] }]*/
const element = <div alt="IMAGE" />
/*eslint lingui/no-unlocalized-strings: ["error", {"strictAttribute": ["alt"]}]*/
const element = <img alt="IMAGE" />
```

### ignoreProperty
#### `regex`

Defines regex patterns for attributes that must always be checked.

Example:

```json
{
"no-unlocalized-strings": [
"error",
{
"strictAttribute": [
{
"regex": {
"pattern": "^desc.*"
}
}
]
}
]
}
```

The `ignoreProperty` option specifies property names not to check.
Examples of **incorrect** code:

Examples of correct code for the `{ "ignoreProperty": ["myProperty"] }` option:
```jsx
const element = <div description="IMAGE" />
```

### `ignoreProperty`

Specifies object property names whose values should be ignored. By default, UPPERCASED properties and `className`, `styleName`, `type`, `id`, `width`, `height`, and `displayName` are ignored.

Example for `{ "ignoreProperty": ["myProperty"] }`:

```jsx
const test = { myProperty: 'This is ignored' }
object.MyProperty = 'This is ignored'
const obj = { myProperty: 'Ignored value' }
obj.myProperty = 'Ignored value'

class MyClass {
myProperty = 'Ignored value'
}
```

#### `regex`

Defines regex patterns for ignored properties.

Example:

```json
{
"no-unlocalized-strings": [
"error",
{
"ignoreProperty": [
{
"regex": {
"pattern": "classname",
"flags": "i"
}
}
]
}
]
}
```

By default, the following properties are ignored: `className`, `styleName`, `type`, `id`, `width`, `height`, `displayName`
Examples of **correct** code:

### ignoreMethodsOnTypes
```jsx
const obj = { wrapperClassName: 'Ignored value' }
obj.wrapperClassName = 'Ignored value'

Leverage the power of TypeScript to exclude methods defined on specific types.
class MyClass {
wrapperClassName = 'Ignored value'
}
```

Note: You must set `useTsTypes: true` to use this option.
### `ignoreMethodsOnTypes`

The method to be excluded is defined as a `Type.method`. The type and method match by name here.
Uses TypeScript type information to ignore methods defined on specific types.

Examples of correct code for the `{ "ignoreMethodsOnTypes": ["Foo.bar"], "useTsTypes": true }` option:
Requires `useTsTypes: true`.

Specify methods as `Type.method`, where both the type and method are matched by name.

Example for `{ "ignoreMethodsOnTypes": ["Foo.get"], "useTsTypes": true }`:

```ts
interface Foo {
get: (key: string) => string
}

const foo: Foo

foo.get('string with a spaces')
foo.get('Some string')
```

The following methods are ignored by default: `Map.get`, `Map.has`, `Set.has`.
28 changes: 27 additions & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export const LinguiCallExpressionMessageQuery =
*/
export const LinguiTransQuery = 'JSXElement[openingElement.name.name=Trans]'

export const UpperCaseRegexp = /^[A-Z_-]+$/

export function isUpperCase(str: string) {
return /^[A-Z_-]+$/.test(str)
return UpperCaseRegexp.test(str)
}

export function isNativeDOMTag(str: string) {
Expand Down Expand Up @@ -117,3 +119,27 @@ export function getIdentifierName(jsxTagNameExpression: TSESTree.JSXTagNameExpre
return null
}
}

export function isLiteral(node: TSESTree.Node | undefined): node is TSESTree.Literal {
return node?.type === TSESTree.AST_NODE_TYPES.Literal
}

export function isTemplateLiteral(
node: TSESTree.Node | undefined,
): node is TSESTree.TemplateLiteral {
return node?.type === TSESTree.AST_NODE_TYPES.TemplateLiteral
}

export function isIdentifier(node: TSESTree.Node | undefined): node is TSESTree.Identifier {
return (node as TSESTree.Node)?.type === TSESTree.AST_NODE_TYPES.Identifier
}

export function isMemberExpression(
node: TSESTree.Node | undefined,
): node is TSESTree.MemberExpression {
return (node as TSESTree.Node)?.type === TSESTree.AST_NODE_TYPES.MemberExpression
}

export function isJSXAttribute(node: TSESTree.Node | undefined): node is TSESTree.JSXAttribute {
return (node as TSESTree.Node)?.type === TSESTree.AST_NODE_TYPES.JSXAttribute
}
3 changes: 2 additions & 1 deletion src/rules/no-single-variables-to-translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TSESTree } from '@typescript-eslint/utils'

import {
getText,
isJSXAttribute,
LinguiCallExpressionMessageQuery,
LinguiTaggedTemplateExpressionMessageQuery,
} from '../helpers'
Expand Down Expand Up @@ -49,7 +50,7 @@ export const rule = createRule({
'JSXElement[openingElement.name.name=Trans]'(node: TSESTree.JSXElement) {
const hasIdProperty =
node.openingElement.attributes.find(
(attr) => attr.type === 'JSXAttribute' && attr.name.name === 'id',
(attr) => isJSXAttribute(attr) && attr.name.name === 'id',
) !== undefined

if (!hasSomeJSXTextWithContent(node.children) && !hasIdProperty) {
Expand Down
Loading

0 comments on commit 1c248a2

Please sign in to comment.