Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-i18next

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-i18next - npm Package Compare versions

Comparing version 5.2.1 to 6.0.0-0

lib/helper/generateFullMatchRegExp.js

13

CHANGELOG.md

@@ -5,2 +5,15 @@ # Changelog

## [6.0.0-0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v5.2.1...v6.0.0-0) (2022-05-15)
### Features
* make mode defaults to jsx-text-only ([7cc75ab](https://github.com/edvardchen/eslint-plugin-i18next/commit/7cc75ab))
* support callees option ([1934216](https://github.com/edvardchen/eslint-plugin-i18next/commit/1934216))
* support option jsx-attributes ([08ae48b](https://github.com/edvardchen/eslint-plugin-i18next/commit/08ae48b))
* support option jsx-components ([99af7e1](https://github.com/edvardchen/eslint-plugin-i18next/commit/99af7e1))
* support option mode ([3193108](https://github.com/edvardchen/eslint-plugin-i18next/commit/3193108))
* support option object-properties ([484fcfb](https://github.com/edvardchen/eslint-plugin-i18next/commit/484fcfb))
* support option words ([874a694](https://github.com/edvardchen/eslint-plugin-i18next/commit/874a694))
### 5.2.1 (2022-05-05)

@@ -7,0 +20,0 @@

106

docs/rules/no-literal-string.md
# disallow literal string (no-literal-string)
This rule aims to avoid developers to display literal string to users
in those projects which need to support [multi-language](https://www.i18next.com/).
This rule aims to avoid developers to display literal string directly to users without translating them.
## Rule Details
It will find out all literal strings and validate them.
Example of incorrect code:
Examples of **incorrect** code for this rule:
```js
```jsx
/*eslint i18next/no-literal-string: "error"*/
const a = 'foo';
<div>hello world</div>
```
Examples of **correct** code for this rule:
Example of correct code:
```js
```jsx
/*eslint i18next/no-literal-string: "error"*/
// safe to assign string to const variables whose name are UPPER_CASE
var FOO = 'foo';
// UPPER_CASE properties are valid no matter if they are computed or not
var a = {
BAR: 'bar',
[FOO]: 'foo'
};
// also safe to use strings themselves are UPPCASE_CASE
var foo = 'FOO';
<div>{i18next.t('HELLO_KEY')}</div>
```
### i18n
## Options
This rule allows to call i18next translate function.
The option's typing definition looks like:
**Correct** code:
```js
/*eslint i18next/no-literal-string: "error"*/
var bar = i18next.t('bar');
var bar2 = i18n.t('bar');
```typescript
type MySchema = {
[key in
| 'words'
| 'jsx-components'
| 'jsx-attributes'
| 'callees'
| 'object-properties'
| 'class-properties']?: {
include?: string[];
exclude?: string[];
};
} & {
mode?: 'jsx-text-only' | 'jsx-only' | 'all';
message?: string;
'should-validate-template'?: boolean;
};
```
### Redux/Vuex
### `exclude` and `include`
This rule also works with those state managers like
[Redux](https://redux.js.org/) and [Vuex](https://vuex.vuejs.org/).
Instead of expanding options immoderately, a standard and scalable way to set options is provided
**Correct** code:
You cam use `exclude` and `include` of each options to control which should be validated and which should be ignored.
```js
/*eslint i18next/no-literal-string: "error"*/
var bar = store.dispatch('bar');
var bar2 = store.commit('bar');
```
The values of these two fields are treated as regular expressions.
## Options
1. If both are used, both conditions need to be satisfied
2. If both are emitted, it will be validated
### ignore
For selectors,
The `ignore` option specifies exceptions not to check for
literal strings that match one of regexp paterns.
- `words` controls plain text
- `jsx-components` controls JSX elements
- `jsx-attributes` controls JSX elements' attributes
- `callees` controls function calls
- `object-properties` controls objects' properties
- `class-properties` controls classes' properties
Examples of correct code for the `{ "ignore": ['foo'] }` option:
Other options,
```js
/*eslint i18next/no-literal-string: ["error", {"ignore": ["foo"]}]*/
const a = 'afoo';
```
- `mode` provides a straightforward way to decides the range you want to validate literal strings.
It defaults to `jsx-text-only` which only forbids to write plain text in JSX markup
- `jsx-only` validates the JSX attributes as well
- `all` validates all literal strings
- `message` defines the custom error message
- `should-validate-template` decides if we should validate the string templates
### ignoreCallee
You can see [the default options here](../../lib/options/defaults.json)
THe `ignoreCallee` option speficies exceptions not check for
function calls whose names match one of regexp patterns.
Examples of correct code for the `{ "ignoreCallee": ["foo"] }` option:
```js
/*eslint i18next/no-literal-string: ["error", { "ignoreCallee": ["foo"] }]*/
const bar = foo('bar');
```
## When Not To Use It
Your project maybe not need to support multi-language or you dont care to spread literal string anywhre.
Your project maybe not need to support multi-language or you don't care to spread literal string anywhere.

@@ -7,8 +7,48 @@ /**

const _ = require('lodash');
const {
isUpperCase,
generateFullMatchRegExp,
getNearestAncestor,
isAllowedDOMAttr,
shouldSkip,
} = require('../helper');
function isValidFunctionCall(context, options, { callee }) {
if (callee.type === 'Import') return true;
const sourceText = context.getSourceCode().getText(callee);
return shouldSkip(options.callees, sourceText);
}
function isValidLiteral(options, { value }) {
if (typeof value !== 'string') {
return true;
}
const trimed = value.trim();
if (!trimed) return true;
if (shouldSkip(options.words, value)) return true;
}
function isValidTypeScriptAnnotation(esTreeNodeToTSNodeMap, typeChecker, node) {
const tsNode = esTreeNodeToTSNodeMap.get(node);
const typeObj = typeChecker.getTypeAtLocation(tsNode.parent);
// var a: 'abc' = 'abc'
if (typeObj.isStringLiteral()) {
return true;
}
// var a: 'abc' | 'name' = 'abc'
if (typeObj.isUnion()) {
const found = typeObj.types.some(item => {
if (item.isStringLiteral() && item.value === node.value) {
return true;
}
});
return found;
}
}
//------------------------------------------------------------------------------

@@ -25,51 +65,3 @@ // Rule Definition

},
schema: [
{
type: 'object',
properties: {
ignore: {
type: 'array',
// string or regexp
},
ignoreAttribute: {
type: 'array',
items: {
type: 'string',
},
},
ignoreCallee: {
type: 'array',
// string or regexp
},
ignoreProperty: {
type: 'array',
items: {
type: 'string',
},
},
ignoreComponent: {
type: 'array',
items: {
type: 'string',
},
},
markupOnly: {
type: 'boolean',
},
message: {
type: 'string',
},
onlyAttribute: {
type: 'array',
items: {
type: 'string',
},
},
validateTemplate: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
schema: [require('../options/schema.json')],
},

@@ -79,22 +71,15 @@

// variables should be defined here
const { parserServices } = context;
const options = _.defaults(
{},
context.options[0],
require('../options/defaults.json')
);
const {
parserServices,
options: [
{
onlyAttribute = [],
markupOnly: _markupOnly,
validateTemplate,
ignoreComponent = [],
ignoreAttribute = [],
ignoreProperty = [],
ignoreCallee = [],
ignore = [],
message = 'disallow literal string',
} = {},
],
} = context;
const whitelists = [
/^[0-9!-/:-@[-`{-~]+$/, // ignore not-word string
...ignore,
].map(item => new RegExp(item));
mode,
'should-validate-template': validateTemplate,
message,
} = options;
const onlyValidateJSX = ['jsx-only', 'jsx-text-only'].includes(mode);

@@ -107,2 +92,6 @@ //----------------------------------------------------------------------

function endIndicator() {
indicatorStack.pop();
}
/**

@@ -115,87 +104,6 @@ * detect if current "scope" is valid

function match(str) {
return whitelists.some(item => item.test(str));
}
const popularCallee = [
/^i18n(ext)?$/,
't',
'require',
'addEventListener',
'removeEventListener',
'postMessage',
'getElementById',
//
// ─── VUEX CALLEE ────────────────────────────────────────────────────────────────
//
'dispatch',
'commit',
// ────────────────────────────────────────────────────────────────────────────────
'includes',
'indexOf',
'endsWith',
'startsWith',
];
const validCalleeList = [...popularCallee, ...ignoreCallee].map(
generateFullMatchRegExp
);
function isValidFunctionCall({ callee }) {
let calleeName = callee.name;
if (callee.type === 'Import') return true;
const sourceText = context.getSourceCode().getText(callee);
return validCalleeList.some(item => {
return item.test(sourceText);
});
}
const ignoredClassProperties = ['displayName'];
const userJSXAttrs = [
'className',
'styleName',
'style',
'type',
'key',
'id',
'width',
'height',
...ignoreAttribute,
];
function isValidAttrName(name) {
if (onlyAttribute.length) {
// only validate those attributes in onlyAttribute option
return !onlyAttribute.includes(name);
}
return userJSXAttrs.includes(name);
}
// Ignore the Trans component for react-i18next compatibility
const ignoredComponents = ['Trans', ...ignoreComponent];
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
const visited = new WeakSet();
function getNearestAncestor(node, type) {
let temp = node.parent;
while (temp) {
if (temp.type === type) {
return temp;
}
temp = temp.parent;
}
return temp;
}
function isString(node) {
return typeof node.value === 'string';
}
const { esTreeNodeToTSNodeMap, program } = parserServices;

@@ -206,29 +114,23 @@ let typeChecker;

function isValidLiteral(str) {
const trimed = str.trim();
if (!trimed) return true;
function report(node) {
context.report({
node,
message: `${message}: ${context.getSourceCode().getText(node.parent)}`,
});
}
// allow statements like const a = "FOO"
if (isUpperCase(trimed)) return true;
function validateBeforeReport(node) {
if (isValidScope()) return;
if (isValidLiteral(options, node)) return;
if (match(trimed)) return true;
}
function validateLiteralNode(node) {
// make sure node is string literal
if (!isString(node)) return;
if (isValidLiteral(node.value)) {
if (
typeChecker &&
isValidTypeScriptAnnotation(esTreeNodeToTSNodeMap, typeChecker, node)
) {
return;
}
context.report({ node, message });
report(node);
}
// onlyAttribute would turn on markOnly
const markupOnly = _markupOnly || !!onlyAttribute.length;
function endIndicator() {
indicatorStack.pop();
}
const scriptVisitor = {

@@ -270,3 +172,3 @@ //

indicatorStack.push(
ignoredComponents.includes(node.openingElement.name.name)
shouldSkip(options['jsx-components'], node.openingElement.name.name)
);

@@ -276,8 +178,18 @@ },

'JSXElement Literal'(node) {
if (mode === 'jsx-only') {
validateBeforeReport(node);
}
},
'JSXElement > Literal'(node) {
scriptVisitor.JSXText(node);
if (mode === 'jsx-text-only') {
validateBeforeReport(node);
}
},
'JSXFragment > Literal'(node) {
scriptVisitor.JSXText(node);
if (onlyValidateJSX) {
validateBeforeReport(node);
}
},

@@ -289,3 +201,3 @@

// allow <MyComponent className="active" />
if (isValidAttrName(attrName)) {
if (shouldSkip(options['jsx-attributes'], attrName)) {
indicatorStack.push(true);

@@ -305,23 +217,5 @@ return;

'JSXAttribute > Literal:exit'(node) {
if (markupOnly) {
if (isValidScope()) return;
validateLiteralNode(node);
}
},
'JSXExpressionContainer > Literal:exit'(node) {
scriptVisitor['JSXAttribute > Literal:exit'](node);
},
// @typescript-eslint/parser would parse string literal as JSXText node
JSXText(node) {
if (isValidScope()) return;
const trimed = node.value.trim();
if (!trimed || match(trimed)) {
return;
}
context.report({ node, message });
validateBeforeReport(node);
},

@@ -353,3 +247,3 @@ // ─────────────────────────────────────────────────────────────────

indicatorStack.push(
!!(node.key && ignoredClassProperties.includes(node.key.name))
!!(node.key && shouldSkip(options['class-properties'], node.key.name))
);

@@ -366,7 +260,8 @@ },

Property(node) {
const result =
ignoreProperty.includes(node.key.name) ||
// name if key is Identifier; value if key is Literal
// dont care whether if this is computed or not
isUpperCase(node.key.name || node.key.value);
// pick up key.name if key is Identifier or key.value if key is Literal
// dont care whether if this is computed or not
const result = shouldSkip(
options['object-properties'],
node.key.name || node.key.value
);
indicatorStack.push(result);

@@ -390,3 +285,3 @@ },

NewExpression(node) {
indicatorStack.push(isValidFunctionCall(node));
indicatorStack.push(isValidFunctionCall(context, options, node));
},

@@ -396,3 +291,3 @@ 'NewExpression:exit': endIndicator,

CallExpression(node) {
indicatorStack.push(isValidFunctionCall(node));
indicatorStack.push(isValidFunctionCall(context, options, node));
},

@@ -419,6 +314,4 @@ 'CallExpression:exit': endIndicator,

quasis.some(({ value: { raw } }) => {
const trimed = raw.trim();
if (!trimed) return;
if (match(trimed)) return;
context.report({ node, message });
if (isValidLiteral(options, { value: raw })) return;
report(node);
return true; // break

@@ -429,2 +322,7 @@ });

'Literal:exit'(node) {
// skip if it only validates jsx
// checking if a literal is contained in a JSX element is hard to be performant
// instead, we validate in jsx-related visitors
if (onlyValidateJSX) return;
// ignore `var a = { "foo": 123 }`

@@ -434,8 +332,3 @@ if (node.parent.key === node) {

}
if (markupOnly) {
return;
}
if (node.parent && node.parent.type === 'JSXElement') return;
if (isValidScope()) return;
validateLiteralNode(node);
validateBeforeReport(node);
},

@@ -442,0 +335,0 @@ };

{
"name": "eslint-plugin-i18next",
"version": "5.2.1",
"version": "6.0.0-0",
"description": "ESLint plugin for i18n",

@@ -24,3 +24,3 @@ "keywords": [

"test:watch": "npm t -- --watch",
"test": "mocha tests --recursive"
"test": "mocha --timeout 50000 tests/lib/rules/no-literal-string/"
},

@@ -27,0 +27,0 @@ "dependencies": {

@@ -5,6 +5,8 @@ # eslint-plugin-i18next

> For old versions below v6, plz refer [this document](./v5.md)
## Installation
```bash
npm install eslint-plugin-i18next --save-dev
npm install eslint-plugin-i18next@next --save-dev
```

@@ -42,316 +44,25 @@

This rule aims to avoid developers to display literal string to users
in those projects which need to support [multi-language](https://www.i18next.com/).
This rule aims to avoid developers to display literal string directly to users without translating them.
> <span style="color: lightcoral">Note:</span> Disable auto-fix because key in the call `i18next.t(key)` ussally was not the same as the literal
> <span style="color: lightcoral">Note:</span> Disable auto-fix because key in the call `i18next.t(key)` usually was not the same as the literal
### Rule Details
Example of incorrect code:
**It will find out all literal strings and validate them.**
Examples of **incorrect** code for this rule:
```js
/*eslint i18next/no-literal-string: "error"*/
const a = 'foo';
<div>hello world</div>
```
Examples of **correct** code for this rule:
Example of correct code:
```js
/*eslint i18next/no-literal-string: "error"*/
// safe to assign string to const variables whose name are UPPER_CASE
var FOO = 'foo';
// UPPER_CASE properties are valid no matter if they are computed or not
var a = {
BAR: 'bar',
[FOO]: 'foo'
};
// also safe to use strings themselves are UPPCASE_CASE
var foo = 'FOO';
<div>{i18next.t('HELLO_KEY')}</div>
```
#### i18n
More options can be found [here](./docs/rules/no-literal-string.md)
This rule allows to call i18next translate function.
### Breaking change
**Correct** code:
```js
/*eslint i18next/no-literal-string: "error"*/
var bar = i18next.t('bar');
var bar2 = i18n.t('bar');
```
Maybe you use other internationalization libraries
not [i18next](https://www.i18next.com/). You can use like this:
```js
/*eslint i18next/no-literal-string: ["error", { "ignoreCallee": ["yourI18n"] }]*/
const bar = yourI18n('bar');
// or
/*eslint i18next/no-literal-string: ["error", { "ignoreCallee": ["yourI18n.method"] }]*/
const bar = yourI18n.method('bar');
```
#### HTML Markup
All literal strings in html template are typically mistakes. For JSX example:
```HTML
<div>foo</div>
```
They should be translated by [i18next translation api](https://www.i18next.com/):
```HTML
<div>{i18next.t('foo')}</div>
```
Same for [Vue template](https://vuejs.org/v2/guide/syntax.html):
```HTML
<!-- incorrect -->
<template>
foo
</template>
<!-- correct -->
<template>
{{ i18next.t('foo') }}
</template>
```
It would allow most reasonable usages of string that would rarely be shown to user, like following examples.
Click on them to see details.
<details>
<summary>
react-i18next
</summary>
This plugin are compatible with [react-i18next](https://react.i18next.com/)
```tsx
// correct
<Trans>
<span>bar</span>
</Trans>
```
</details>
<details>
<summary>
Redux/Vuex
</summary>
This rule also works with those state managers like
[Redux](https://redux.js.org/) and [Vuex](https://vuex.vuejs.org/).
**Correct** code:
```js
var bar = store.dispatch('bar');
var bar2 = store.commit('bar');
```
</details>
<details>
<summary>
Typescript
</summary>
This plugin would not complain on those reasonable usages of string.
The following cases are considered as **correct**:
```typescript
var a: Type['member'];
var a: Omit<T, 'key'>;
enum E {
A = 1
}
var a = E['A'];
var a: { t: 'button' } = { t: 'button' };
var a: 'abc' | 'name' = 'abc';
```
We require type information to work properly, so you need to add some options in your `.eslintrc`:
```js
"parserOptions": {
// path of your tsconfig.json
"project": "./tsconfig.json"
}
```
See
[here](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage)
for more deteils.
</details>
<details>
<summary>
Import/Export
</summary>
The following cases are **allowed**:
```typescript
import mod from 'm';
import('mod');
require('mod');
export { named } from 'm';
export * from 'm';
```
</details>
<details>
<summary>
String Comparison
</summary>
String comparison is fine.
```typescript
// correct
name === 'Android' || name === 'iOS';
```
</details>
<details>
<summary>
SwithCase
</summary>
Skip switchcase statement:
```typescript
// correct
switch (type) {
case 'foo':
break;
case 'bar':
break;
}
```
</details>
### Options
### markupOnly
If `markupOnly` option turn on, only JSX text and strings used as JSX attributes will be validated.
JSX text:
```jsx
// incorrect
<div>hello world</div>
<div>{"hello world"}</div>
```
Strings as JSX attribute:
```jsx
// incorrect
<div foo="foo"></div>
<div foo={"foo"}></div>
```
### onlyAttribute
Only check the `JSX` attributes that listed in this option. **This option would turn on `markupOnly`.**
```jsx
// correct
const foo = 'bar';
<div foo="foo"></div>
// incorrect
<div>foo</div>
/*eslint i18next/no-literal-string: ["error", {"onlyAttribute": ["foo"]}]*/
// incorrect
<div foo="foo"></div>
```
#### ignore
The `ignore` option specifies exceptions not to check for
literal strings that match one of regexp paterns.
Examples of correct code for the `{ "ignore": ['foo'] }` option:
```js
/*eslint i18next/no-literal-string: ["error", {"ignore": ["foo"]}]*/
const a = 'afoo';
```
#### ignoreCallee
THe `ignoreCallee` option speficies exceptions not check for
function calls whose names match one of regexp patterns.
Examples of correct code for the `{ "ignoreCallee": ["foo"] }` option:
```js
/*eslint i18next/no-literal-string: ["error", { "ignoreCallee": ["foo"] }]*/
const bar = foo('bar');
```
#### ignoreAttribute
The `ignoreAttribute` option specifies exceptions not to check for JSX attributes that match one of ignored attributes.
Examples of correct code for the `{ "ignoreAttribute": ["foo"] }` option:
```jsx
/*eslint i18next/no-literal-string: ["error", { "ignoreAttribute": ["foo"] }]*/
const element = <div foo="bar" />;
```
#### ignoreProperty
The `ignoreProperty` option specifies exceptions not to check for object properties that match one of ignored properties.
Examples of correct code for the `{ "ignoreProperty": ["foo"] }` option:
```jsx
/*eslint i18next/no-literal-string: ["error", { "ignoreProperty": ["foo"] }]*/
const a = { foo: 'bar' };
```
#### ignoreComponent
The `ignoreComponent` option specifies exceptions not to check for string literals inside a list of named components. It includes `<Trans>` per default.
Examples of correct code for the `{ "ignoreComponent": ["Icon"] }` option:
```jsx
/*eslint i18next/no-literal-string: ["error", { "ignoreComponent": ["Icon"] }]*/
<Icon>arrow<Icon/>
```
#### validateTemplate
Indicate whether to validate [template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) or not. Default `false`
```js
/*eslint i18next/no-literal-string: ["error", { "validateTemplate": true }]*/
// incorrect
var foo = `hello world`;
```
By default, it wil only validate the plain text in JSX markup instead of all literal strings in previous versions.
[You can change it easily](./docs/rules/no-literal-string.md)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc