From 13e649e75f2d35b121c80be59f2931e901d0e3f5 Mon Sep 17 00:00:00 2001 From: Bharat Soni Date: Tue, 29 Jan 2019 15:39:14 +0800 Subject: [PATCH] [New]: `jsx-no-literals`: add `ignoreProps` option to ignore props validation Co-authored-by: Bharat Soni Co-authored-by: Serg Hospodarets --- docs/rules/jsx-no-literals.md | 45 +++++++++++++++- lib/rules/jsx-no-literals.js | 62 ++++++++++++++++----- tests/lib/rules/jsx-no-literals.js | 87 +++++++++++++++++------------- 3 files changed, 143 insertions(+), 51 deletions(-) diff --git a/docs/rules/jsx-no-literals.md b/docs/rules/jsx-no-literals.md index ab920844df..66a26d56ea 100644 --- a/docs/rules/jsx-no-literals.md +++ b/docs/rules/jsx-no-literals.md @@ -28,13 +28,14 @@ var Hello =
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": [, {"noStrings": true, "allowedStrings": ["allowed"]}] +"react/jsx-no-literals": [, {"noStrings": true, "allowedStrings": ["allowed"], "ignoreProps": false}] ``` In this configuration, the following are considered warnings: @@ -53,6 +54,19 @@ var Hello =
; ``` +```jsx +var Hello =
; +``` + +```jsx +var Hello =
; +``` + +```jsx +var Hello =
; +``` + + The following are **not** considered warnings: ```jsx @@ -77,6 +91,33 @@ var Hello =
; ``` +```jsx +// spread props object +var Hello = +``` + +```jsx +// use variable for prop values +var Hello =
+``` + +```jsx +// cache +class Comp1 extends Component { + asdf() {} + + render() { + return ( +
+ {'asdjfl'} + test + {'foo'} +
+ ); + } +} +``` + ## When Not To Use It If you do not want to enforce any style JSX literals, then you can disable this rule. diff --git a/lib/rules/jsx-no-literals.js b/lib/rules/jsx-no-literals.js index f2bb83dd01..e4b1ab8eba 100644 --- a/lib/rules/jsx-no-literals.js +++ b/lib/rules/jsx-no-literals.js @@ -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: { @@ -33,6 +37,9 @@ module.exports = { items: { type: 'string' } + }, + ignoreProps: { + type: 'boolean' } }, additionalProperties: false @@ -40,11 +47,7 @@ module.exports = { }, 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)); @@ -52,10 +55,12 @@ module.exports = { '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()}”` }); } @@ -82,18 +87,47 @@ 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); @@ -101,12 +135,16 @@ module.exports = { }, 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); } } - }; } }; diff --git a/tests/lib/rules/jsx-no-literals.js b/tests/lib/rules/jsx-no-literals.js index b620571b7a..c8851b2e24 100644 --- a/tests/lib/rules/jsx-no-literals.js +++ b/tests/lib/rules/jsx-no-literals.js @@ -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, { @@ -140,7 +144,7 @@ ruleTester.run('jsx-no-literals', rule, { `, parser: parsers.BABEL_ESLINT, - options: [{noStrings: true}] + options: [{noStrings: true, ignoreProps: true}] }, { code: ` @@ -148,21 +152,21 @@ ruleTester.run('jsx-no-literals', rule, { `, parser: parsers.BABEL_ESLINT, - options: [{noStrings: true}] + options: [{noStrings: true, ignoreProps: true}] }, { code: ` {intl.formatText(message)} `, - options: [{noStrings: true}] + options: [{noStrings: true, ignoreProps: true}] }, { code: ` {translate('my.translate.key')} `, - options: [{noStrings: true}] + options: [{noStrings: true, ignoreProps: true}] }, { code: '', options: [{noStrings: true}] @@ -183,11 +187,11 @@ ruleTester.run('jsx-no-literals', rule, { class Comp1 extends Component { asdf() {} render() { - return ; + return ; } } `, - options: [{noStrings: true}] + options: [{noStrings: true, ignoreProps: true}] }, { code: ` class Comp1 extends Component { @@ -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 ; + } + } `, + parser: parsers.BABEL_ESLINT, + options: [{noStrings: true, ignoreProps: false}] } ], @@ -369,42 +385,33 @@ ruleTester.run('jsx-no-literals', rule, { {'Test'} `, - parser: parsers.BABEL_ESLINT, - options: [{noStrings: true}], - errors: [{message: stringsMessage('\'Test\'')}] - }, { - code: ` - - {'Test'} - - `, - options: [{noStrings: true}], - errors: [{message: stringsMessage('\'Test\'')}] + options: [{noStrings: true, ignoreProps: false}], + errors: [ + {message: invalidProp('bar="test"')}, + {message: stringsMessage('\'Test\'')} + ] }, { code: ` {'Test' + name} `, - options: [{noStrings: true}], - errors: [{message: stringsMessage('\'Test\'')}] - }, { - code: ` - - Test - - `, - 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: ` Test `, - options: [{noStrings: true}], - errors: [{message: stringsMessage('Test')}] + options: [{noStrings: true, ignoreProps: false}], + errors: [ + {message: invalidProp('bar="test"')}, + {message: stringsMessage('Test')} + ] }, { code: ` @@ -415,33 +422,33 @@ ruleTester.run('jsx-no-literals', rule, { errors: [{message: stringsMessage('`Test`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [{message: stringsMessage('`Test`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [{message: stringsMessage('`${baz}`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [{message: stringsMessage('`Test ${baz}`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [ {message: stringsMessage('`foo`')}, {message: stringsMessage('\'bar\'')} ] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [ {message: stringsMessage('`foo`')}, {message: stringsMessage('`bar`')} ] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, ignoreProps: false}], errors: [ {message: stringsMessage('\'foo\'')}, {message: stringsMessage('`bar`')} @@ -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: '', + options: [{noStrings: true, ignoreProps: false}], + errors: [ + {message: stringsMessage('\'bar\'')} + ] } ] });