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 55b605f commit cf2210c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 21 deletions.
47 changes: 45 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.
* `validateProps`(`false` default) - Enforces no 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"], "validateProps": true}]
```

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 @@ -65,6 +79,7 @@ var Hello = <div><Text {...message} /></div>
var Hello = <div>{translate('my.translation.key')}</div>
```


```jsx
// an allowed string
var Hello = <div>allowed</div>
Expand All @@ -77,6 +92,34 @@ 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'
}
},
validateProps: {
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: [], validateProps: 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.validateProps)) {
reportLiteralNode(node);
}
},

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

if (config.noStrings && config.validateProps && isNodeValueString) {
const customMessage = 'Invalid attribute 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.validateProps)) {
reportLiteralNode(node);
}
}

};
}
};
44 changes: 37 additions & 7 deletions tests/lib/rules/jsx-no-literals.js
Expand Up @@ -183,7 +183,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 Down Expand Up @@ -260,6 +260,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, validateProps: true}]
}
],

Expand Down Expand Up @@ -415,33 +427,33 @@ 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`')}
Expand All @@ -455,10 +467,28 @@ ruleTester.run('jsx-no-literals', rule, {
}
`,
options: [{noStrings: true, allowedStrings: ['asd']}],
errors: [
{message: stringsMessage('asdf')}
]
}, {
code: `
class Comp1 extends Component {
render() {
return <div bar={'foo'}>asdf</div>
}
}
`,
options: [{noStrings: true, allowedStrings: ['asd'], validateProps: true}],
errors: [
{message: stringsMessage('\'foo\'')},
{message: stringsMessage('asdf')}
]
}, {
code: '<Foo bar={\'bar\'} />',
options: [{noStrings: true, validateProps: true}],
errors: [
{message: stringsMessage('\'bar\'')}
]
}
]
});

0 comments on commit cf2210c

Please sign in to comment.