Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New]: jsx-no-literals: add ignoreProps option to ignore props validation #2146

Merged
merged 1 commit into from Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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` (default: `false`) - 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` (default: `false`) - 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`')}]
iiison marked this conversation as resolved.
Show resolved Hide resolved
}, {
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}],
iiison marked this conversation as resolved.
Show resolved Hide resolved
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\'')}
]
}
]
});