Skip to content

Commit

Permalink
[New]: jsx-no-literals: add ignoreProps option to ignore props va…
Browse files Browse the repository at this point in the history
…lidation

Co-authored-by: Bharat Soni <i.bharat.soni@gmail.com>
Co-authored-by: Serg Hospodarets <shospodarets@gmail.com>
  • Loading branch information
2 people authored and ljharb committed Jan 29, 2019
1 parent 185d95c commit 13e649e
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 51 deletions.
45 changes: 43 additions & 2 deletions docs/rules/jsx-no-literals.md
Expand Up @@ -28,13 +28,14 @@ var Hello = <div>

There are two options:

* `noStrings` - Enforces no string literals used as children, wrapped or unwrapped.
* `noStrings`(`false` default) - Enforces no string literals used as children, wrapped or unwrapped.
* `allowedStrings` - An array of unique string values that would otherwise warn, but will be ignored.
* `ignoreProps`(`false` default) - When `true` the rule ignores literals used in props, wrapped or unwrapped.

To use, you can specify as follows:

```js
"react/jsx-no-literals": [<enabled>, {"noStrings": true, "allowedStrings": ["allowed"]}]
"react/jsx-no-literals": [<enabled>, {"noStrings": true, "allowedStrings": ["allowed"], "ignoreProps": false}]
```

In this configuration, the following are considered warnings:
Expand All @@ -53,6 +54,19 @@ var Hello = <div>
</div>;
```

```jsx
var Hello = <div class='xx' />;
```

```jsx
var Hello = <div class={'xx'} />;
```

```jsx
var Hello = <div class={`xx`} />;
```


The following are **not** considered warnings:

```jsx
Expand All @@ -77,6 +91,33 @@ var Hello = <div>
</div>;
```

```jsx
// spread props object
var Hello = <Text {...props} />
```

```jsx
// use variable for prop values
var Hello = <div class={xx} />
```

```jsx
// cache
class Comp1 extends Component {
asdf() {}

render() {
return (
<div onClick={this.asdf}>
{'asdjfl'}
test
{'foo'}
</div>
);
}
}
```

## When Not To Use It

If you do not want to enforce any style JSX literals, then you can disable this rule.
62 changes: 50 additions & 12 deletions lib/rules/jsx-no-literals.js
Expand Up @@ -12,6 +12,10 @@ const docsUrl = require('../util/docsUrl');
// Rule Definition
// ------------------------------------------------------------------------------

function trimIfString(val) {
return typeof val === 'string' ? val.trim() : val;
}

module.exports = {
meta: {
docs: {
Expand All @@ -33,29 +37,30 @@ module.exports = {
items: {
type: 'string'
}
},
ignoreProps: {
type: 'boolean'
}
},
additionalProperties: false
}]
},

create(context) {
function trimIfString(val) {
return typeof val === 'string' ? val.trim() : val;
}

const defaults = {noStrings: false, allowedStrings: []};
const defaults = {noStrings: false, allowedStrings: [], ignoreProps: false};
const config = Object.assign({}, defaults, context.options[0] || {});
config.allowedStrings = new Set(config.allowedStrings.map(trimIfString));

const message = config.noStrings ?
'Strings not allowed in JSX files' :
'Missing JSX expression container around literal string';

function reportLiteralNode(node) {
function reportLiteralNode(node, customMessage) {
const errorMessage = customMessage || message;

context.report({
node,
message: `${message}: “${context.getSourceCode().getText(node).trim()}”`
message: `${errorMessage}: “${context.getSourceCode().getText(node).trim()}”`
});
}

Expand All @@ -82,31 +87,64 @@ module.exports = {
return standard && parent.type !== 'JSXExpressionContainer';
}

function getParentAndGrandParentType(node) {
const parent = getParentIgnoringBinaryExpressions(node);
const parentType = parent.type;
const grandParentType = parent.parent.type;

return {
parent,
parentType,
grandParentType,
grandParent: parent.parent
};
}

function hasJSXElementParentOrGrandParent(node) {
const parents = getParentAndGrandParentType(node);
const parentType = parents.parentType;
const grandParentType = parents.grandParentType;

return parentType === 'JSXFragment' || parentType === 'JSXElement' || grandParentType === 'JSXElement';
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

return {

Literal(node) {
if (getValidation(node)) {
if (getValidation(node) && (hasJSXElementParentOrGrandParent(node) || !config.ignoreProps)) {
reportLiteralNode(node);
}
},

JSXAttribute(node) {
const isNodeValueString = node.value && node.value && node.value.type === 'Literal' && typeof node.value.value === 'string';

if (config.noStrings && !config.ignoreProps && isNodeValueString) {
const customMessage = 'Invalid prop value';
reportLiteralNode(node, customMessage);
}
},

JSXText(node) {
if (getValidation(node)) {
reportLiteralNode(node);
}
},

TemplateLiteral(node) {
const parent = getParentIgnoringBinaryExpressions(node);
if (config.noStrings && parent.type === 'JSXExpressionContainer') {
const parents = getParentAndGrandParentType(node);
const parentType = parents.parentType;
const grandParentType = parents.grandParentType;
const isParentJSXExpressionCont = parentType === 'JSXExpressionContainer';
const isParentJSXElement = parentType === 'JSXElement' || grandParentType === 'JSXElement';

if (isParentJSXExpressionCont && config.noStrings && (isParentJSXElement || !config.ignoreProps)) {
reportLiteralNode(node);
}
}

};
}
};
87 changes: 50 additions & 37 deletions tests/lib/rules/jsx-no-literals.js
Expand Up @@ -35,6 +35,10 @@ function jsxMessage(str) {
return `Missing JSX expression container around literal string: “${str}”`;
}

function invalidProp(str) {
return `Invalid prop value: “${str}”`;
}

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-no-literals', rule, {

Expand Down Expand Up @@ -140,29 +144,29 @@ ruleTester.run('jsx-no-literals', rule, {
</Foo>
`,
parser: parsers.BABEL_ESLINT,
options: [{noStrings: true}]
options: [{noStrings: true, ignoreProps: true}]
}, {
code: `
<Foo bar="test">
{translate('my.translate.key')}
</Foo>
`,
parser: parsers.BABEL_ESLINT,
options: [{noStrings: true}]
options: [{noStrings: true, ignoreProps: true}]
}, {
code: `
<Foo bar="test">
{intl.formatText(message)}
</Foo>
`,
options: [{noStrings: true}]
options: [{noStrings: true, ignoreProps: true}]
}, {
code: `
<Foo bar="test">
{translate('my.translate.key')}
</Foo>
`,
options: [{noStrings: true}]
options: [{noStrings: true, ignoreProps: true}]
}, {
code: '<Foo bar={true} />',
options: [{noStrings: true}]
Expand All @@ -183,11 +187,11 @@ ruleTester.run('jsx-no-literals', rule, {
class Comp1 extends Component {
asdf() {}
render() {
return <Foo bar={this.asdf} />;
return <Foo bar={this.asdf} class='xx' />;
}
}
`,
options: [{noStrings: true}]
options: [{noStrings: true, ignoreProps: true}]
}, {
code: `
class Comp1 extends Component {
Expand Down Expand Up @@ -260,6 +264,18 @@ ruleTester.run('jsx-no-literals', rule, {
}
`,
options: [{noStrings: true, allowedStrings: [' foo ']}]
}, {
code: `
class Comp1 extends Component {
asdf() {}
render() {
const xx = 'xx';
return <Foo bar={this.asdf} class={xx} />;
}
} `,
parser: parsers.BABEL_ESLINT,
options: [{noStrings: true, ignoreProps: false}]
}
],

Expand Down Expand Up @@ -369,42 +385,33 @@ ruleTester.run('jsx-no-literals', rule, {
{'Test'}
</Foo>
`,
parser: parsers.BABEL_ESLINT,
options: [{noStrings: true}],
errors: [{message: stringsMessage('\'Test\'')}]
}, {
code: `
<Foo bar="test">
{'Test'}
</Foo>
`,
options: [{noStrings: true}],
errors: [{message: stringsMessage('\'Test\'')}]
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: invalidProp('bar="test"')},
{message: stringsMessage('\'Test\'')}
]
}, {
code: `
<Foo bar="test">
{'Test' + name}
</Foo>
`,
options: [{noStrings: true}],
errors: [{message: stringsMessage('\'Test\'')}]
}, {
code: `
<Foo bar="test">
Test
</Foo>
`,
parser: parsers.BABEL_ESLINT,
options: [{noStrings: true}],
errors: [{message: stringsMessage('Test')}]
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: invalidProp('bar="test"')},
{message: stringsMessage('\'Test\'')}
]
}, {
code: `
<Foo bar="test">
Test
</Foo>
`,
options: [{noStrings: true}],
errors: [{message: stringsMessage('Test')}]
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: invalidProp('bar="test"')},
{message: stringsMessage('Test')}
]
}, {
code: `
<Foo>
Expand All @@ -415,33 +422,33 @@ ruleTester.run('jsx-no-literals', rule, {
errors: [{message: stringsMessage('`Test`')}]
}, {
code: '<Foo bar={`Test`} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [{message: stringsMessage('`Test`')}]
}, {
code: '<Foo bar={`${baz}`} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [{message: stringsMessage('`${baz}`')}]
}, {
code: '<Foo bar={`Test ${baz}`} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [{message: stringsMessage('`Test ${baz}`')}]
}, {
code: '<Foo bar={`foo` + \'bar\'} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: stringsMessage('`foo`')},
{message: stringsMessage('\'bar\'')}
]
}, {
code: '<Foo bar={`foo` + `bar`} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: stringsMessage('`foo`')},
{message: stringsMessage('`bar`')}
]
}, {
code: '<Foo bar={\'foo\' + `bar`} />',
options: [{noStrings: true}],
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: stringsMessage('\'foo\'')},
{message: stringsMessage('`bar`')}
Expand All @@ -454,11 +461,17 @@ ruleTester.run('jsx-no-literals', rule, {
}
}
`,
options: [{noStrings: true, allowedStrings: ['asd']}],
options: [{noStrings: true, allowedStrings: ['asd'], ignoreProps: false}],
errors: [
{message: stringsMessage('\'foo\'')},
{message: stringsMessage('asdf')}
]
}, {
code: '<Foo bar={\'bar\'} />',
options: [{noStrings: true, ignoreProps: false}],
errors: [
{message: stringsMessage('\'bar\'')}
]
}
]
});

0 comments on commit 13e649e

Please sign in to comment.