From 448f86c03fe3f15a6ad062f3b3ba2d866f202cb1 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 `validateProps` option to ignore props validation --- docs/rules/jsx-no-literals.md | 52 +++++++++++++++++++++++-- lib/rules/jsx-no-literals.js | 62 ++++++++++++++++++++++++------ tests/lib/rules/jsx-no-literals.js | 44 +++++++++++++++++---- 3 files changed, 136 insertions(+), 22 deletions(-) diff --git a/docs/rules/jsx-no-literals.md b/docs/rules/jsx-no-literals.md index ab920844df..4a223582f5 100644 --- a/docs/rules/jsx-no-literals.md +++ b/docs/rules/jsx-no-literals.md @@ -28,16 +28,17 @@ 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. +* `validateProps`(`false` default) - Enforces no 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"], "validateProps": true}] ``` -In this configuration, the following are considered warnings: +With `noStrings` set to `true`, the following are considered warnings: ```jsx var Hello =
test
; @@ -65,6 +66,7 @@ var Hello =
var Hello =
{translate('my.translation.key')}
``` + ```jsx // an allowed string var Hello =
allowed
@@ -77,6 +79,50 @@ var Hello =
; ``` +With `validateProps` set to `true`, the following are considered warnings: + +```jsx +var Hello =
; +``` + +```jsx +var Hello =
; +``` + +```jsx +var Hello =
; +``` + +The following are **not** considered warnings: + +```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..11ca0d3f58 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' } + }, + validateProps: { + 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: [], validateProps: 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.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); @@ -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.validateProps)) { reportLiteralNode(node); } } - }; } }; diff --git a/tests/lib/rules/jsx-no-literals.js b/tests/lib/rules/jsx-no-literals.js index b620571b7a..ada3cd6ea6 100644 --- a/tests/lib/rules/jsx-no-literals.js +++ b/tests/lib/rules/jsx-no-literals.js @@ -183,7 +183,7 @@ ruleTester.run('jsx-no-literals', rule, { class Comp1 extends Component { asdf() {} render() { - return ; + return ; } } `, @@ -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 ; + } + } `, + parser: parsers.BABEL_ESLINT, + options: [{noStrings: true, validateProps: true}] } ], @@ -415,33 +427,33 @@ ruleTester.run('jsx-no-literals', rule, { errors: [{message: stringsMessage('`Test`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [{message: stringsMessage('`Test`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [{message: stringsMessage('`${baz}`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [{message: stringsMessage('`Test ${baz}`')}] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [ {message: stringsMessage('`foo`')}, {message: stringsMessage('\'bar\'')} ] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [ {message: stringsMessage('`foo`')}, {message: stringsMessage('`bar`')} ] }, { code: '', - options: [{noStrings: true}], + options: [{noStrings: true, validateProps: true}], errors: [ {message: stringsMessage('\'foo\'')}, {message: stringsMessage('`bar`')} @@ -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
asdf
+ } + } + `, + options: [{noStrings: true, allowedStrings: ['asd'], validateProps: true}], errors: [ {message: stringsMessage('\'foo\'')}, {message: stringsMessage('asdf')} ] + }, { + code: '', + options: [{noStrings: true, validateProps: true}], + errors: [ + {message: stringsMessage('\'bar\'')} + ] } ] });