Skip to content

Commit

Permalink
[New]: jsx-no-literals: add validateProps option to ignore props …
Browse files Browse the repository at this point in the history
…validation
  • Loading branch information
iiison authored and ljharb committed Jan 29, 2019
1 parent 6e4f43e commit 8a1eb1e
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 20 deletions.
54 changes: 50 additions & 4 deletions docs/rules/jsx-no-literals.md
Expand Up @@ -21,17 +21,18 @@ var Hello = <div>{'test'}</div>;

### Options

There is only one option:
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.
* `validateProps`(`false` default) - Enforces no literals used in props, wrapped or unwrapped.

To use, you can specify like the following:

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

In this configuration, the following are considered warnings:
With `noStrings` set to `true`, the following are considered warnings:

```jsx
var Hello = <div>test</div>;
Expand All @@ -53,6 +54,51 @@ var Hello = <div><Text {...message} /></div>
var Hello = <div>{translate('my.translation.key')}</div>
```

With `validateProps` set to `true`, the following are considered warnings:

```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
// 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.

61 changes: 53 additions & 8 deletions lib/rules/jsx-no-literals.js
Expand Up @@ -25,24 +25,36 @@ module.exports = {
properties: {
noStrings: {
type: 'boolean'
},
validateProps: {
type: 'boolean'
}
},
additionalProperties: false
}]
},

create: function(context) {
const isNoStrings = context.options[0] ? context.options[0].noStrings : false;
const options = context.options;
const configs = options[0] || {};

const noStrings = configs.noStrings || false;
const validateProps = configs.validateProps || false;

const isNoStrings = noStrings;
const shouldValidateProps = validateProps;
const sourceCode = context.getSourceCode();

const message = isNoStrings ?
'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: node,
message: `${message}: “${sourceCode.getText(node).trim()}”`
message: `${errorMessage}: “${sourceCode.getText(node).trim()}”`
});
}

Expand All @@ -66,31 +78,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: parent,
parentType: parentType,
grandParentType: 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: function(node) {
if (getValidation(node)) {
if (getValidation(node) && (hasJSXElementParentOrGrandParent(node) || shouldValidateProps)) {
reportLiteralNode(node);
}
},

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

if (isNoStrings && shouldValidateProps && isNodeValueString) {
const customMessage = 'Invalid attribute value';
reportLiteralNode(node, customMessage);
}
},

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

TemplateLiteral: function(node) {
const parent = getParentIgnoringBinaryExpressions(node);
if (isNoStrings && 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 && isNoStrings && (isParentJSXElement || shouldValidateProps)) {
reportLiteralNode(node);
}
}

};
}
};
33 changes: 25 additions & 8 deletions tests/lib/rules/jsx-no-literals.js
Expand Up @@ -182,7 +182,7 @@ ruleTester.run('jsx-no-literals', rule, {
class Comp1 extends Component {
asdf() {}
render() {
return <Foo bar={this.asdf} />;
return <Foo bar={this.asdf} class='xx' />;
}
}
`,
Expand All @@ -197,8 +197,19 @@ ruleTester.run('jsx-no-literals', rule, {
}
`,
options: [{noStrings: true}]
}
}, {
code: `
class Comp1 extends Component {
asdf() {}
render() {
const xx = 'xx';
return <Foo bar={this.asdf} class={xx} />;
}
} `,
parser: 'babel-eslint',
options: [{noStrings: true, validateProps: true}]
}
],

invalid: [
Expand Down Expand Up @@ -353,37 +364,43 @@ ruleTester.run('jsx-no-literals', rule, {
errors: [{message: stringsMessage('`Test`')}]
}, {
code: '<Foo bar={`Test`} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [{message: stringsMessage('`Test`')}]
}, {
code: '<Foo bar={`${baz}`} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [{message: stringsMessage('`${baz}`')}]
}, {
code: '<Foo bar={`Test ${baz}`} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [{message: stringsMessage('`Test ${baz}`')}]
}, {
code: '<Foo bar={`foo` + \'bar\'} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [
{message: stringsMessage('`foo`')},
{message: stringsMessage('\'bar\'')}
]
}, {
code: '<Foo bar={`foo` + `bar`} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [
{message: stringsMessage('`foo`')},
{message: stringsMessage('`bar`')}
]
}, {
code: '<Foo bar={\'foo\' + `bar`} />',
options: [{noStrings: true}],
options: [{noStrings: true, validateProps: true}],
errors: [
{message: stringsMessage('\'foo\'')},
{message: stringsMessage('`bar`')}
]
}, {
code: '<Foo bar={\'bar\'} />',
options: [{noStrings: true, validateProps: true}],
errors: [
{message: stringsMessage('\'bar\'')}
]
}
]
});

0 comments on commit 8a1eb1e

Please sign in to comment.