From 2a595fdda0053f86aa3f1c768e7a7e3cc59bbfaf Mon Sep 17 00:00:00 2001 From: Dustin Yoste Date: Fri, 11 Oct 2019 12:29:51 -0500 Subject: [PATCH 01/36] [Fix] `jsx-curly-brace-presence`: Fix filter of undefined error with whitespace inside jsx attr curlies --- lib/rules/jsx-curly-brace-presence.js | 6 ++++++ tests/lib/rules/jsx-curly-brace-presence.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 76dc70fb4e..661fe52337 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -242,12 +242,18 @@ module.exports = { } function hasAdjacentJsxExpressionContainers(node, children) { + if (!children) { + return false; + } const childrenExcludingWhitespaceLiteral = children.filter(child => !isWhiteSpaceLiteral(child)); const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral); return adjSiblings.some(x => x.type && x.type === 'JSXExpressionContainer'); } function hasAdjacentJsx(node, children) { + if (!children) { + return false; + } const childrenExcludingWhitespaceLiteral = children.filter(child => !isWhiteSpaceLiteral(child)); const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral); diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 380d06f787..c6dce207d0 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -145,6 +145,9 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: `{'foo \\n bar'}` }, + { + code: `` + }, { code: `foo`, options: [{props: 'never'}] From 7fa31dfe690719b58a3037f6fecb3454d6976f4e Mon Sep 17 00:00:00 2001 From: Lance Ivy Date: Wed, 2 Oct 2019 13:46:25 -0700 Subject: [PATCH 02/36] [Fix] `jsx-no-literals`: trim whitespace for `allowedStrings` check --- docs/rules/jsx-no-literals.md | 30 +++++++++++++++++++++------ lib/rules/jsx-no-literals.js | 17 +++++++++------ tests/lib/rules/jsx-no-literals.js | 33 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/docs/rules/jsx-no-literals.md b/docs/rules/jsx-no-literals.md index 0b1b977dd8..ab920844df 100644 --- a/docs/rules/jsx-no-literals.md +++ b/docs/rules/jsx-no-literals.md @@ -1,11 +1,10 @@ # Prevent usage of string literals in JSX (react/jsx-no-literals) -There are a couple of scenarios where you want to avoid string literals in JSX. Either to enforce consistency and reducing strange behaviour, or for enforcing that literals aren't kept in JSX so they can be translated. +There are a few scenarios where you want to avoid string literals in JSX. You may want to enforce consistency, reduce syntax highlighting issues, or ensure that strings are part of a translation system. ## Rule Details -In JSX when using a literal string you can wrap it in a JSX container `{'TEXT'}`. This rules by default requires that you wrap all literal strings. -Prevents any odd artifacts of highlighters if your unwrapped string contains an enclosing character like `'` in contractions and enforces consistency. +By default this rule requires that you wrap all literal strings in a JSX container `{'TEXT'}`. The following patterns are considered warnings: @@ -19,14 +18,20 @@ The following patterns are **not** considered warnings: var Hello =
{'test'}
; ``` -### Options +```jsx +var Hello =
+ {'test'} +
; +``` + +## Rule Options There are two options: * `noStrings` - 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. +* `allowedStrings` - An array of unique string values that would otherwise warn, but will be ignored. -To use, you can specify like the following: +To use, you can specify as follows: ```js "react/jsx-no-literals": [, {"noStrings": true, "allowedStrings": ["allowed"]}] @@ -42,6 +47,12 @@ var Hello =
test
; var Hello =
{'test'}
; ``` +```jsx +var Hello =
+ {'test'} +
; +``` + The following are **not** considered warnings: ```jsx @@ -59,6 +70,13 @@ var Hello =
{translate('my.translation.key')}
var Hello =
allowed
``` +```jsx +// an allowed string surrounded by only whitespace +var Hello =
+ allowed +
; +``` + ## 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 fd4d91be06..f2bb83dd01 100644 --- a/lib/rules/jsx-no-literals.js +++ b/lib/rules/jsx-no-literals.js @@ -40,10 +40,15 @@ module.exports = { }, create(context) { - const isNoStrings = context.options[0] ? context.options[0].noStrings : false; - const allowedStrings = context.options[0] ? new Set(context.options[0].allowedStrings) : false; + function trimIfString(val) { + return typeof val === 'string' ? val.trim() : val; + } + + const defaults = {noStrings: false, allowedStrings: []}; + const config = Object.assign({}, defaults, context.options[0] || {}); + config.allowedStrings = new Set(config.allowedStrings.map(trimIfString)); - const message = isNoStrings ? + const message = config.noStrings ? 'Strings not allowed in JSX files' : 'Missing JSX expression container around literal string'; @@ -63,7 +68,7 @@ module.exports = { } function getValidation(node) { - if (allowedStrings && allowedStrings.has(node.value)) { + if (config.allowedStrings.has(trimIfString(node.value))) { return false; } const parent = getParentIgnoringBinaryExpressions(node); @@ -71,7 +76,7 @@ module.exports = { typeof node.value === 'string' && parent.type.indexOf('JSX') !== -1 && parent.type !== 'JSXAttribute'; - if (isNoStrings) { + if (config.noStrings) { return standard; } return standard && parent.type !== 'JSXExpressionContainer'; @@ -97,7 +102,7 @@ module.exports = { TemplateLiteral(node) { const parent = getParentIgnoringBinaryExpressions(node); - if (isNoStrings && parent.type === 'JSXExpressionContainer') { + if (config.noStrings && parent.type === 'JSXExpressionContainer') { reportLiteralNode(node); } } diff --git a/tests/lib/rules/jsx-no-literals.js b/tests/lib/rules/jsx-no-literals.js index 5f1267757e..b620571b7a 100644 --- a/tests/lib/rules/jsx-no-literals.js +++ b/tests/lib/rules/jsx-no-literals.js @@ -207,6 +207,15 @@ ruleTester.run('jsx-no-literals', rule, { } `, options: [{allowedStrings: ['asdf']}] + }, { + code: ` + class Comp1 extends Component { + render() { + return
asdf
+ } + } + `, + options: [{noStrings: false, allowedStrings: ['asdf']}] }, { code: ` @@ -218,6 +227,20 @@ ruleTester.run('jsx-no-literals', rule, { `, options: [{noStrings: true, allowedStrings: [' ']}] }, + { + code: ` + class Comp1 extends Component { + render() { + return ( +
+   +
+ ); + } + } + `, + options: [{noStrings: true, allowedStrings: [' ']}] + }, { code: ` class Comp1 extends Component { @@ -227,6 +250,16 @@ ruleTester.run('jsx-no-literals', rule, { } `, options: [{noStrings: true, allowedStrings: ['foo: ', '*']}] + }, + { + code: ` + class Comp1 extends Component { + render() { + return
foo
+ } + } + `, + options: [{noStrings: true, allowedStrings: [' foo ']}] } ], From 11dc56bf83199fc314d22ca8a3a85ff437dd6475 Mon Sep 17 00:00:00 2001 From: Moroine Bentefrit Date: Sat, 5 Oct 2019 14:14:16 +0800 Subject: [PATCH 03/36] [New] `prop-types`: Support Flow Type spread Fixes #2138. --- lib/rules/prop-types.js | 2 +- lib/util/ast.js | 3 + lib/util/propTypes.js | 42 +++++-- .../rules/default-props-match-prop-types.js | 117 +++++++++++++++++- tests/lib/rules/no-unused-prop-types.js | 23 ++++ tests/lib/rules/prop-types.js | 65 ++++++++++ 6 files changed, 242 insertions(+), 10 deletions(-) diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index c652e5bc12..aa53691567 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -103,7 +103,7 @@ module.exports = { return true; } // Consider every children as declared - if (propType.children === true || propType.containsSpread || propType.containsIndexers) { + if (propType.children === true || propType.containsUnresolvedSpread || propType.containsIndexers) { return true; } if (propType.acceptedProperties) { diff --git a/lib/util/ast.js b/lib/util/ast.js index 13d9ff8997..1111c992d9 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -165,6 +165,9 @@ function getKeyValue(context, node) { stripQuotes(tokens[0].value) ); } + if (node.type === 'GenericTypeAnnotation') { + return node.id.name; + } const key = node.key || node.argument; return key.type === 'Identifier' ? key.name : key.value; } diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index b03607b73c..c60f92c767 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -32,13 +32,19 @@ function isSuperTypeParameterPropsDeclaration(node) { * @param {Object[]} properties Array of properties to iterate. * @param {Function} fn Function to call on each property, receives property key and property value. (key, value) => void + * @param {Function} [handleSpreadFn] Function to call on each ObjectTypeSpreadProperty, receives the + argument */ -function iterateProperties(context, properties, fn) { +function iterateProperties(context, properties, fn, handleSpreadFn) { if (properties && properties.length && typeof fn === 'function') { for (let i = 0, j = properties.length; i < j; i++) { const node = properties[i]; const key = getKeyValue(context, node); + if (node.type === 'ObjectTypeSpreadProperty' && typeof handleSpreadFn === 'function') { + handleSpreadFn(node.argument); + } + const value = node.value; fn(key, value, node); } @@ -121,7 +127,8 @@ module.exports = function propTypesInstructions(context, components, utils) { }, ObjectTypeAnnotation(annotation, parentName, seen) { - let containsObjectTypeSpread = false; + let containsUnresolvedObjectTypeSpread = false; + let containsSpread = false; const containsIndexers = Boolean(annotation.indexers && annotation.indexers.length); const shapeTypeDefinition = { type: 'shape', @@ -129,9 +136,7 @@ module.exports = function propTypesInstructions(context, components, utils) { }; iterateProperties(context, annotation.properties, (childKey, childValue, propNode) => { const fullName = [parentName, childKey].join('.'); - if (!childKey && !childValue) { - containsObjectTypeSpread = true; - } else { + if (childKey || childValue) { const types = buildTypeAnnotationDeclarationTypes(childValue, fullName, seen); types.fullName = fullName; types.name = childKey; @@ -139,12 +144,24 @@ module.exports = function propTypesInstructions(context, components, utils) { types.isRequired = !childValue.optional; shapeTypeDefinition.children[childKey] = types; } + }, + (spreadNode) => { + const key = getKeyValue(context, spreadNode); + const types = buildTypeAnnotationDeclarationTypes(spreadNode, key, seen); + if (!types.children) { + containsUnresolvedObjectTypeSpread = true; + } else { + Object.assign(shapeTypeDefinition, types.children); + } + containsSpread = true; }); // Mark if this shape has spread or an indexer. We will know to consider all props from this shape as having propTypes, // but still have the ability to detect unused children of this shape. - shapeTypeDefinition.containsSpread = containsObjectTypeSpread; + shapeTypeDefinition.containsUnresolvedSpread = containsUnresolvedObjectTypeSpread; shapeTypeDefinition.containsIndexers = containsIndexers; + // Deprecated: containsSpread is not used anymore in the codebase, ensure to keep API backward compatibility + shapeTypeDefinition.containsSpread = containsSpread; return shapeTypeDefinition; }, @@ -241,7 +258,7 @@ module.exports = function propTypesInstructions(context, components, utils) { } /** - * Marks all props found inside ObjectTypeAnnotaiton as declared. + * Marks all props found inside ObjectTypeAnnotation as declared. * * Modifies the declaredProperties object * @param {ASTNode} propTypes @@ -253,7 +270,7 @@ module.exports = function propTypesInstructions(context, components, utils) { iterateProperties(context, propTypes.properties, (key, value, propNode) => { if (!value) { - ignorePropsValidation = true; + ignorePropsValidation = ignorePropsValidation || propNode.type !== 'ObjectTypeSpreadProperty'; return; } @@ -263,6 +280,15 @@ module.exports = function propTypesInstructions(context, components, utils) { types.node = propNode; types.isRequired = !propNode.optional; declaredPropTypes[key] = types; + }, (spreadNode) => { + const key = getKeyValue(context, spreadNode); + const spreadAnnotation = getInTypeScope(key); + if (!spreadAnnotation) { + ignorePropsValidation = true; + } else { + const spreadIgnoreValidation = declarePropTypesForObjectTypeAnnotation(spreadAnnotation, declaredPropTypes); + ignorePropsValidation = ignorePropsValidation || spreadIgnoreValidation; + } }); return ignorePropsValidation; diff --git a/tests/lib/rules/default-props-match-prop-types.js b/tests/lib/rules/default-props-match-prop-types.js index 0e6c6d0be6..5e244cbec7 100644 --- a/tests/lib/rules/default-props-match-prop-types.js +++ b/tests/lib/rules/default-props-match-prop-types.js @@ -733,6 +733,58 @@ ruleTester.run('default-props-match-prop-types', rule, { ].join('\n'), parser: parsers.BABEL_ESLINT }, + { + code: [ + 'type DefaultProps1 = {|', + ' bar1?: string', + '|};', + 'type DefaultProps2 = {|', + ' ...DefaultProps1,', + ' bar2?: string', + '|};', + 'type Props = {', + ' foo: string,', + ' ...DefaultProps2', + '};', + + 'function Hello(props: Props) {', + ' return
Hello {props.foo}
;', + '}', + + 'Hello.defaultProps = {', + ' bar1: "bar1",', + ' bar2: "bar2",', + '};' + ].join('\n'), + parser: parsers.BABEL_ESLINT + }, + { + code: [ + 'type DefaultProps1 = {|', + ' bar1?: string', + '|};', + 'type DefaultProps2 = {|', + ' ...DefaultProps1,', + ' bar2?: string', + '|};', + 'type Props = {', + ' foo: string,', + ' ...DefaultProps2', + '};', + + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello {props.foo}
;', + ' }', + '}', + + 'Hello.defaultProps = {', + ' bar1: "bar1",', + ' bar2: "bar2",', + '};' + ].join('\n'), + parser: parsers.BABEL_ESLINT + }, // don't error when variable is not in scope { code: [ @@ -1460,7 +1512,6 @@ ruleTester.run('default-props-match-prop-types', rule, { column: 3 }] }, - // Investigate why this test fails. Flow type not finding foo? { code: [ 'function Hello(props: { foo: string }) {', @@ -1590,6 +1641,70 @@ ruleTester.run('default-props-match-prop-types', rule, { message: 'defaultProp "firstProperty" defined for isRequired propType.' } ] + }, + { + code: [ + 'type DefaultProps = {', + ' baz?: string,', + ' bar?: string', + '};', + + 'type Props = {', + ' foo: string,', + ' ...DefaultProps', + '}', + + 'function Hello(props: Props) {', + ' return
Hello {props.foo}
;', + '}', + 'Hello.defaultProps = { foo: "foo", frob: "frob", baz: "bar" };' + ].join('\n'), + parser: parsers.BABEL_ESLINT, + errors: [ + { + message: 'defaultProp "foo" defined for isRequired propType.', + line: 12, + column: 24 + }, + { + message: 'defaultProp "frob" has no corresponding propTypes declaration.', + line: 12, + column: 36 + } + ] + }, + { + code: [ + 'type DefaultProps = {', + ' baz?: string,', + ' bar?: string', + '};', + + 'type Props = {', + ' foo: string,', + ' ...DefaultProps', + '}', + + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello {props.foo}
;', + ' }', + '}', + 'Hello.defaultProps = { foo: "foo", frob: "frob", baz: "bar" };' + ].join('\n'), + parser: parsers.BABEL_ESLINT, + errors: [ + { + message: 'defaultProp "foo" defined for isRequired propType.', + line: 14, + column: 24 + }, + { + message: 'defaultProp "frob" has no corresponding propTypes declaration.', + line: 14, + column: 36 + } + ] } ] }); diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index b5ba74ea25..df76b3117e 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -4619,6 +4619,29 @@ ruleTester.run('no-unused-prop-types', rule, { errors: [{ message: '\'aProp\' PropType is defined but prop is never used' }] + }, { + // issue #2138 + code: ` + type UsedProps = {| + usedProp: number, + |}; + + type UnusedProps = {| + unusedProp: number, + |}; + + type Props = {| ...UsedProps, ...UnusedProps |}; + + function MyComponent({ usedProp, notOne }: Props) { + return
{usedProp}
; + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: "'unusedProp' PropType is defined but prop is never used", + line: 7, + column: 23 + }] }, { code: ` type Props = { diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index dedf5ff727..7f533b4d26 100755 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -1632,6 +1632,23 @@ ruleTester.run('prop-types', rule, { '}' ].join('\n'), parser: parsers.BABEL_ESLINT + }, { + code: [ + 'type OtherProps = {', + ' firstname: string,', + '};', + 'type Props = {', + ' ...OtherProps,', + ' lastname: string', + '};', + 'class Hello extends React.Component {', + ' props: Props;', + ' render () {', + ' return
Hello {this.props.firstname}
;', + ' }', + '}' + ].join('\n'), + parser: parsers.BABEL_ESLINT }, { code: [ 'type Person = {', @@ -2335,6 +2352,30 @@ ruleTester.run('prop-types', rule, { `, parser: parsers.BABEL_ESLINT }, + { + // issue #2138 + code: ` + type UsedProps = {| + usedProp: number, + |}; + + type UnusedProps = {| + unusedProp: number, + |}; + + type Props = {| ...UsedProps, ...UnusedProps |}; + + function MyComponent({ usedProp }: Props) { + return
{usedProp}
; + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: "'notOne' is missing in props validation", + line: 8, + column: 34 + }] + }, { // issue #1259 code: ` @@ -4728,6 +4769,30 @@ ruleTester.run('prop-types', rule, { message: '\'initialValues\' is missing in props validation' }] }, + { + // issue #2138 + code: ` + type UsedProps = {| + usedProp: number, + |}; + + type UnusedProps = {| + unusedProp: number, + |}; + + type Props = {| ...UsedProps, ...UnusedProps |}; + + function MyComponent({ usedProp, notOne }: Props) { + return
{usedProp}
; + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: "'notOne' is missing in props validation", + line: 12, + column: 42 + }] + }, { // issue #2298 code: ` From 9e4ad2130f2050655ce144d5c77a385062b1b5b1 Mon Sep 17 00:00:00 2001 From: "Serdar.Mustafa" <45297391+SerdarMustafa1@users.noreply.github.com> Date: Thu, 17 Oct 2019 09:34:48 +0300 Subject: [PATCH 04/36] [Docs] `jsx-first-prop-new-line`: add rule options --- docs/rules/jsx-first-prop-new-line.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/rules/jsx-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md index fc6481642a..e414d953a4 100644 --- a/docs/rules/jsx-first-prop-new-line.md +++ b/docs/rules/jsx-first-prop-new-line.md @@ -99,6 +99,12 @@ The following patterns are **not** considered warnings when configured `"multili /> ``` +## Rule Options + +...jsx +"react/jsx-max-props-per-line": `"always" | "never" | "multiline" | "multiline-multiprop"` +... + ## When not to use If you are not using JSX then you can disable this rule. From 39e4396e3f74bdeaf170cfb202b6407eb6f155f2 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Tue, 22 Oct 2019 18:14:19 +0800 Subject: [PATCH 05/36] [Docs] `jsx-props-no-multi-spaces`: suggest using core rule instead --- docs/rules/jsx-props-no-multi-spaces.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rules/jsx-props-no-multi-spaces.md b/docs/rules/jsx-props-no-multi-spaces.md index c1b02dec45..933e3cb5d9 100644 --- a/docs/rules/jsx-props-no-multi-spaces.md +++ b/docs/rules/jsx-props-no-multi-spaces.md @@ -29,3 +29,5 @@ The following patterns are **not** considered warnings: ## When Not To Use It If you are not using JSX or don't care about the space between two props in the same line. + +If you have enabled the core rule `no-multi-spaces` with eslint >= 3, you don't need this rule. From d9d21934427ee3c710d37aa341483dc0b82f12d4 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Sun, 20 Oct 2019 18:43:53 +0800 Subject: [PATCH 06/36] [eslint] enable eslint-plugin-eslint-plugin --- .eslintrc | 3 +- lib/rules/jsx-child-element-spacing.js | 2 +- package.json | 1 + .../rules/default-props-match-prop-types.js | 45 ++++++++++--------- tests/lib/rules/destructuring-assignment.js | 18 +++----- tests/lib/rules/jsx-curly-brace-presence.js | 3 +- tests/lib/rules/jsx-curly-spacing.js | 9 ++-- tests/lib/rules/jsx-no-undef.js | 3 +- .../lib/rules/jsx-one-expression-per-line.js | 20 +-------- tests/lib/rules/jsx-pascal-case.js | 7 ++- tests/lib/rules/jsx-wrap-multilines.js | 2 +- .../no-redundant-should-component-update.js | 9 ++-- tests/lib/rules/no-typos.js | 29 +----------- tests/lib/rules/no-unused-prop-types.js | 34 -------------- tests/lib/rules/prop-types.js | 3 +- tests/lib/rules/react-in-jsx-scope.js | 5 ++- tests/lib/rules/require-default-props.js | 20 +-------- 17 files changed, 63 insertions(+), 150 deletions(-) diff --git a/.eslintrc b/.eslintrc index a221189379..4d2d2a26fb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "root": true, - "extends": "airbnb-base", + "extends": ["airbnb-base", "plugin:eslint-plugin/recommended"], + "plugins": ["eslint-plugin"], "env": { "es6": true, "node": true diff --git a/lib/rules/jsx-child-element-spacing.js b/lib/rules/jsx-child-element-spacing.js index 0d0756fef0..6c78257626 100644 --- a/lib/rules/jsx-child-element-spacing.js +++ b/lib/rules/jsx-child-element-spacing.js @@ -45,7 +45,7 @@ module.exports = { recommended: false, url: docsUrl('jsx-child-element-spacing') }, - fixable: false, + fixable: null, schema: [ { type: 'object', diff --git a/package.json b/package.json index 64c0ffd72d..e88979f187 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", + "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", "jsx-ast-utils": "^2.2.1", "object.entries": "^1.1.0", diff --git a/tests/lib/rules/default-props-match-prop-types.js b/tests/lib/rules/default-props-match-prop-types.js index 5e244cbec7..9b449f0ae3 100644 --- a/tests/lib/rules/default-props-match-prop-types.js +++ b/tests/lib/rules/default-props-match-prop-types.js @@ -268,7 +268,8 @@ ruleTester.run('default-props-match-prop-types', rule, { 'Greeting.defaultProps = {', ' foo: "foo"', '};' - ].join('\n') + ].join('\n'), + parser: parsers.BABEL_ESLINT }, { code: [ @@ -530,7 +531,8 @@ ruleTester.run('default-props-match-prop-types', rule, { ' ...defaults,', ' bar: "bar"', '};' - ].join('\n') + ].join('\n'), + parser: parsers.BABEL_ESLINT }, // @@ -845,6 +847,26 @@ ruleTester.run('default-props-match-prop-types', rule, { column: 3 }] }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' baz: "baz"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 9, + column: 3 + }], + parser: parsers.BABEL_ESLINT + }, { code: [ 'function MyStatelessComponent({ foo, bar }) {', @@ -1348,25 +1370,6 @@ ruleTester.run('default-props-match-prop-types', rule, { column: 5 }] }, - { - code: [ - 'function MyStatelessComponent({ foo, bar }) {', - ' return
{foo}{bar}
;', - '}', - 'MyStatelessComponent.propTypes = {', - ' foo: React.PropTypes.string,', - ' bar: React.PropTypes.string.isRequired', - '};', - 'MyStatelessComponent.defaultProps = {', - ' baz: "baz"', - '};' - ].join('\n'), - errors: [{ - message: 'defaultProp "baz" has no corresponding propTypes declaration.', - line: 9, - column: 3 - }] - }, { code: [ 'class Greeting extends React.Component {', diff --git a/tests/lib/rules/destructuring-assignment.js b/tests/lib/rules/destructuring-assignment.js index 5aa5d2873b..b8eaefe620 100644 --- a/tests/lib/rules/destructuring-assignment.js +++ b/tests/lib/rules/destructuring-assignment.js @@ -21,15 +21,6 @@ const parserOptions = { const ruleTester = new RuleTester({parserOptions}); ruleTester.run('destructuring-assignment', rule, { valid: [{ - code: `const Foo = class extends React.PureComponent { - render() { - const { foo } = this.props; - return
{foo}
; - } - };`, - options: ['always'], - parser: parsers.BABEL_ESLINT - }, { code: `const MyComponent = ({ id, className }) => (
);` @@ -102,15 +93,16 @@ ruleTester.run('destructuring-assignment', rule, { return
{foo}
; } };`, - options: ['always'], - parser: parsers.BABEL_ESLINT + options: ['always'] }, { code: `const Foo = class extends React.PureComponent { render() { const { foo } = this.props; return
{foo}
; } - };` + };`, + options: ['always'], + parser: parsers.BABEL_ESLINT }, { code: `const Foo = class extends React.PureComponent { render() { @@ -119,7 +111,7 @@ ruleTester.run('destructuring-assignment', rule, { } };`, options: ['always'], - parser: parsers.BABEL_ESLINT + parser: parsers.TYPESCRIPT_ESLINT }, { code: `const MyComponent = (props) => { const { h, i } = hi; diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index c6dce207d0..1e137a6c8e 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -508,7 +508,8 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: `foo`, output: `foo`, options: [{props: 'always'}], - errors: [{message: missingCurlyMessage}] + errors: [{message: missingCurlyMessage}], + parser: parsers.BABEL_ESLINT }, { code: 'foo bar ', diff --git a/tests/lib/rules/jsx-curly-spacing.js b/tests/lib/rules/jsx-curly-spacing.js index 859ac71365..4a38804354 100644 --- a/tests/lib/rules/jsx-curly-spacing.js +++ b/tests/lib/rules/jsx-curly-spacing.js @@ -405,7 +405,8 @@ ruleTester.run('jsx-curly-spacing', rule, { '...bar', '} />;' ].join('\n'), - options: [{attributes: {when: 'never'}}] + options: [{attributes: {when: 'never'}}], + parser: parsers.BABEL_ESLINT }, { code: [ '{bar}', options: [{attributes: {when: 'always'}}] - }, { - code: '{bar}', - options: [{attributes: {when: 'always'}}] }, { code: [ '', @@ -630,7 +628,8 @@ ruleTester.run('jsx-curly-spacing', rule, { '...bar', '} />;' ].join('\n'), - options: ['always'] + options: ['always'], + parser: parsers.BABEL_ESLINT }, { code: [ ');' }, { - code: '/*eslint no-undef:1*/ var React, App; React.render();' + code: '/*eslint no-undef:1*/ var React, App; React.render();', + parser: parsers.BABEL_ESLINT }, { code: '/*eslint no-undef:1*/ var React; React.render();' }, { diff --git a/tests/lib/rules/jsx-one-expression-per-line.js b/tests/lib/rules/jsx-one-expression-per-line.js index 2778d16e42..92c3e93898 100644 --- a/tests/lib/rules/jsx-one-expression-per-line.js +++ b/tests/lib/rules/jsx-one-expression-per-line.js @@ -522,23 +522,6 @@ ruleTester.run('jsx-one-expression-per-line', rule, { ].join('\n'), errors: [{message: '` bar` must be placed on a new line'}], parserOptions - }, { - code: [ - '
', - ' foo {"bar"}', - '
' - ].join('\n'), - output: [ - '
', - ' foo ', - '{\' \'}', - '{"bar"}', - '
' - ].join('\n'), - errors: [ - {message: '`{"bar"}` must be placed on a new line'} - ], - parserOptions }, { code: [ '
', @@ -705,7 +688,8 @@ ruleTester.run('jsx-one-expression-per-line', rule, { '' ].join('\n'), errors: [{message: '`Foo` must be placed on a new line'}], - parserOptions + parserOptions, + parser: parsers.BABEL_ESLINT }, { code: [ '', diff --git a/tests/lib/rules/jsx-pascal-case.js b/tests/lib/rules/jsx-pascal-case.js index f0965730f2..f15c9b633d 100644 --- a/tests/lib/rules/jsx-pascal-case.js +++ b/tests/lib/rules/jsx-pascal-case.js @@ -12,6 +12,8 @@ const RuleTester = require('eslint').RuleTester; const rule = require('../../../lib/rules/jsx-pascal-case'); +const parsers = require('../../helpers/parsers'); + const parserOptions = { ecmaVersion: 2018, sourceType: 'module', @@ -46,6 +48,9 @@ ruleTester.run('jsx-pascal-case', rule, { code: '' }, { code: '' + }, { + code: '', + parser: parsers.BABEL_ESLINT }, { code: '', options: [{allowAllCaps: true}] @@ -59,8 +64,6 @@ ruleTester.run('jsx-pascal-case', rule, { }, { code: '', options: [{ignore: ['IGNORED']}] - }, { - code: '' }, { code: '<$ />' }, { diff --git a/tests/lib/rules/jsx-wrap-multilines.js b/tests/lib/rules/jsx-wrap-multilines.js index cdf0718689..ff43136f2b 100644 --- a/tests/lib/rules/jsx-wrap-multilines.js +++ b/tests/lib/rules/jsx-wrap-multilines.js @@ -1083,7 +1083,7 @@ ruleTester.run('jsx-wrap-multilines', rule, { ] }, { code: DECLARATION_TERNARY_PAREN_FRAGMENT, - parser: parsers.BABEL_ESLINT, + parser: parsers.TYPESCRIPT_ESLINT, output: addNewLineSymbols(DECLARATION_TERNARY_PAREN_FRAGMENT), options: [{declaration: 'parens-new-line'}], errors: [ diff --git a/tests/lib/rules/no-redundant-should-component-update.js b/tests/lib/rules/no-redundant-should-component-update.js index 34c9724413..de3a37518b 100644 --- a/tests/lib/rules/no-redundant-should-component-update.js +++ b/tests/lib/rules/no-redundant-should-component-update.js @@ -44,22 +44,23 @@ ruleTester.run('no-redundant-should-component-update', rule, { { code: ` class Foo extends React.Component { - shouldComponentUpdate = () => { + shouldComponentUpdate() { return true; } } `, - parser: parsers.BABEL_ESLINT, - parserOptions + parserOptions, + parser: parsers.BABEL_ESLINT }, { code: ` class Foo extends React.Component { - shouldComponentUpdate() { + shouldComponentUpdate = () => { return true; } } `, + parser: parsers.BABEL_ESLINT, parserOptions }, { diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 0a001e304e..24e982fd08 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -333,19 +333,6 @@ ruleTester.run('no-typos', rule, { `, parser: parsers.BABEL_ESLINT, parserOptions - }, { - code: ` - import PropTypes from "prop-types"; - class Component extends React.Component {}; - Component.propTypes = { - a: PropTypes.oneOf([ - 'hello', - 'hi' - ]) - } - `, - parser: parsers.BABEL_ESLINT, - parserOptions }, { code: ` import PropTypes from "prop-types"; @@ -406,21 +393,6 @@ ruleTester.run('no-typos', rule, { } `, parserOptions - }, { - code: ` - import PropTypes from "prop-types"; - class Component extends React.Component {}; - Component.childContextTypes = { - a: PropTypes.string, - b: PropTypes.string.isRequired, - c: PropTypes.shape({ - d: PropTypes.string, - e: PropTypes.number.isRequired, - }).isRequired - } - `, - parser: parsers.BABEL_ESLINT, - parserOptions }, { code: ` import PropTypes from "prop-types"; @@ -1366,6 +1338,7 @@ ruleTester.run('no-typos', rule, { }).isrequired } `, + parser: parsers.BABEL_ESLINT, parserOptions, errors: [{ message: 'Typo in prop type chain qualifier: isrequired' diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index df76b3117e..0b32121c48 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -5025,40 +5025,6 @@ ruleTester.run('no-unused-prop-types', rule, { errors: [{ message: '\'lastname\' PropType is defined but prop is never used' }] - }, { - code: [ - 'type Person = {', - ' ...data,', - ' lastname: string', - '};', - 'class Hello extends React.Component {', - ' props: Person;', - ' render () {', - ' return
Hello {this.props.firstname}
;', - ' }', - '}' - ].join('\n'), - parser: parsers.BABEL_ESLINT, - errors: [{ - message: '\'lastname\' PropType is defined but prop is never used' - }] - }, { - code: [ - 'type Person = {|', - ' ...data,', - ' lastname: string', - '|};', - 'class Hello extends React.Component {', - ' props: Person;', - ' render () {', - ' return
Hello {this.props.firstname}
;', - ' }', - '}' - ].join('\n'), - parser: parsers.BABEL_ESLINT, - errors: [{ - message: '\'lastname\' PropType is defined but prop is never used' - }] }, { code: [ 'class Hello extends React.Component {', diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 7f533b4d26..b55fd6e0db 100755 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -1448,7 +1448,8 @@ ruleTester.run('prop-types', rule, { ' }', '});' ].join('\n'), - options: [{skipUndeclared: true}] + options: [{skipUndeclared: true}], + parser: parsers.BABEL_ESLINT }, { code: [ 'class Hello extends React.Component {', diff --git a/tests/lib/rules/react-in-jsx-scope.js b/tests/lib/rules/react-in-jsx-scope.js index 4371074d48..47f206ddf3 100644 --- a/tests/lib/rules/react-in-jsx-scope.js +++ b/tests/lib/rules/react-in-jsx-scope.js @@ -41,7 +41,10 @@ ruleTester.run('react-in-jsx-scope', rule, { {code: 'var React; ;'}, {code: 'var React, App, a=1; ;'}, {code: 'var React, App, a=1; function elem() { return ; }'}, - {code: 'var React, App; ;'}, + { + code: 'var React, App; ;', + parser: parsers.BABEL_ESLINT + }, {code: '/** @jsx Foo */ var Foo, App; ;'}, {code: '/** @jsx Foo.Bar */ var Foo, App; ;'}, { diff --git a/tests/lib/rules/require-default-props.js b/tests/lib/rules/require-default-props.js index e537414f2c..c691961ed7 100644 --- a/tests/lib/rules/require-default-props.js +++ b/tests/lib/rules/require-default-props.js @@ -514,7 +514,8 @@ ruleTester.run('require-default-props', rule, { ' ...defaults,', ' bar: "bar"', '};' - ].join('\n') + ].join('\n'), + parser: parsers.BABEL_ESLINT }, // @@ -1789,23 +1790,6 @@ ruleTester.run('require-default-props', rule, { column: 47 }] }, - { - code: [ - 'type Props = {', - ' foo?: string', - '};', - - 'function Hello(props: Props) {', - ' return
Hello {props.foo}
;', - '}' - ].join('\n'), - parser: parsers.BABEL_ESLINT, - errors: [{ - message: 'propType "foo" is not required, but has no corresponding defaultProps declaration.', - line: 2, - column: 3 - }] - }, { code: [ 'type Props = {', From 5be539b9d1b3595b81b081f2b4b98e174530e66f Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Sun, 20 Oct 2019 18:19:18 +0800 Subject: [PATCH 07/36] [Fix] `no-typos`: improve report location --- lib/rules/no-typos.js | 15 +++++++-------- tests/lib/rules/no-typos.js | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index 96be82b943..3c943ad068 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -124,9 +124,10 @@ module.exports = { } } - function reportErrorIfPropertyCasingTypo(node, propertyName, isClassProperty) { + function reportErrorIfPropertyCasingTypo(propertyValue, propertyKey, isClassProperty) { + const propertyName = propertyKey.name; if (propertyName === 'propTypes' || propertyName === 'contextTypes' || propertyName === 'childContextTypes') { - checkValidPropObject(node); + checkValidPropObject(propertyValue); } STATIC_CLASS_PROPERTIES.forEach((CLASS_PROP) => { if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) { @@ -134,7 +135,7 @@ module.exports = { 'Typo in static class property declaration' : 'Typo in property declaration'; context.report({ - node, + node: propertyKey, message }); } @@ -176,9 +177,7 @@ module.exports = { return; } - const tokens = context.getFirstTokens(node, 2); - const propertyName = tokens[1].value; - reportErrorIfPropertyCasingTypo(node.value, propertyName, true); + reportErrorIfPropertyCasingTypo(node.value, node.key, true); }, MemberExpression(node) { @@ -198,7 +197,7 @@ module.exports = { (utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node)) && (node.parent && node.parent.type === 'AssignmentExpression' && node.parent.right) ) { - reportErrorIfPropertyCasingTypo(node.parent.right, propertyName, true); + reportErrorIfPropertyCasingTypo(node.parent.right, node.property, true); } }, @@ -218,7 +217,7 @@ module.exports = { } node.properties.forEach((property) => { - reportErrorIfPropertyCasingTypo(property.value, property.key.name, false); + reportErrorIfPropertyCasingTypo(property.value, property.key, false); reportErrorIfLifecycleMethodCasingTypo(property); }); } diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 24e982fd08..bb53d72444 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -618,21 +618,21 @@ ruleTester.run('no-typos', rule, { `, parser: parsers.BABEL_ESLINT, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` class Component extends React.Component {} Component.PropTypes = {} `, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` function MyComponent() { return (
{this.props.myProp}
) } MyComponent.PropTypes = {} `, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` class Component extends React.Component { @@ -664,7 +664,7 @@ ruleTester.run('no-typos', rule, { `, parser: parsers.BABEL_ESLINT, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` class Component extends React.Component {} @@ -756,21 +756,21 @@ ruleTester.run('no-typos', rule, { `, parser: parsers.BABEL_ESLINT, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` class Component extends React.Component {} Component.DefaultProps = {} `, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` function MyComponent() { return (
{this.props.myProp}
) } MyComponent.DefaultProps = {} `, parserOptions, - errors: [{message: ERROR_MESSAGE}] + errors: [{message: ERROR_MESSAGE, type: 'Identifier'}] }, { code: ` class Component extends React.Component { @@ -1567,13 +1567,13 @@ ruleTester.run('no-typos', rule, { parserOptions, errors: [{ message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_LIFECYCLE_METHOD, type: 'Property' @@ -1619,13 +1619,13 @@ ruleTester.run('no-typos', rule, { parserOptions, errors: [{ message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_ES5, - type: 'ObjectExpression' + type: 'Identifier' }, { message: ERROR_MESSAGE_LIFECYCLE_METHOD, type: 'Property' From 809356582ce1123c90bc7a7e9740b0bb7a6eb38f Mon Sep 17 00:00:00 2001 From: Aubrey Holland Date: Mon, 21 Oct 2019 16:08:42 -0400 Subject: [PATCH 08/36] [New] `jsx-handler-names`: add `checkLocalVariables` option --- docs/rules/jsx-handler-names.md | 4 +++- lib/rules/jsx-handler-names.js | 21 ++++++++++++++----- tests/lib/rules/jsx-handler-names.js | 30 ++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/docs/rules/jsx-handler-names.md b/docs/rules/jsx-handler-names.md index b567b414db..42d285ae99 100644 --- a/docs/rules/jsx-handler-names.md +++ b/docs/rules/jsx-handler-names.md @@ -30,13 +30,15 @@ The following patterns are **not** considered warnings: ... "react/jsx-handler-names": [, { "eventHandlerPrefix": , - "eventHandlerPropPrefix": + "eventHandlerPropPrefix": , + "checkLocalVariables": }] ... ``` * `eventHandlerPrefix`: Prefix for component methods used as event handlers. Defaults to `handle` * `eventHandlerPropPrefix`: Prefix for props that are used as event handlers. Defaults to `on` +* `checkLocalVariables`: Determines whether event handlers stored as local variables are checked. Defaults to `false` ## When Not To Use It diff --git a/lib/rules/jsx-handler-names.js b/lib/rules/jsx-handler-names.js index 29d8666f48..7c2b34a4c1 100644 --- a/lib/rules/jsx-handler-names.js +++ b/lib/rules/jsx-handler-names.js @@ -21,12 +21,13 @@ module.exports = { }, schema: [{ - oneOf: [ + anyOf: [ { type: 'object', properties: { eventHandlerPrefix: {type: 'string'}, - eventHandlerPropPrefix: {type: 'string'} + eventHandlerPropPrefix: {type: 'string'}, + checkLocalVariables: {type: 'boolean'} }, additionalProperties: false }, { @@ -36,7 +37,8 @@ module.exports = { eventHandlerPropPrefix: { type: 'boolean', enum: [false] - } + }, + checkLocalVariables: {type: 'boolean'} }, additionalProperties: false }, { @@ -46,7 +48,14 @@ module.exports = { type: 'boolean', enum: [false] }, - eventHandlerPropPrefix: {type: 'string'} + eventHandlerPropPrefix: {type: 'string'}, + checkLocalVariables: {type: 'boolean'} + }, + additionalProperties: false + }, { + type: 'object', + properties: { + checkLocalVariables: {type: 'boolean'} }, additionalProperties: false } @@ -75,9 +84,11 @@ module.exports = { null : new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`); + const checkLocal = !!configuration.checkLocalVariables; + return { JSXAttribute(node) { - if (!node.value || !node.value.expression || !node.value.expression.object) { + if (!node.value || !node.value.expression || (!checkLocal && !node.value.expression.object)) { return; } diff --git a/tests/lib/rules/jsx-handler-names.js b/tests/lib/rules/jsx-handler-names.js index c05988d271..d3f54ac704 100644 --- a/tests/lib/rules/jsx-handler-names.js +++ b/tests/lib/rules/jsx-handler-names.js @@ -32,6 +32,16 @@ ruleTester.run('jsx-handler-names', rule, { code: '' }, { code: '' + }, { + code: '', + options: [{ + checkLocalVariables: true + }] + }, { + code: '', + options: [{ + checkLocalVariables: false + }] }, { code: '' }, { @@ -99,12 +109,32 @@ ruleTester.run('jsx-handler-names', rule, { }, { code: '', errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}] + }, { + code: '', + errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}], + options: [{ + checkLocalVariables: true + }] }, { code: '', errors: [{message: 'Prop key for handleChange must begin with \'on\''}] }, { code: '', errors: [{message: 'Prop key for handleChange must begin with \'on\''}] + }, { + code: '', + errors: [{message: 'Prop key for handleChange must begin with \'on\''}], + options: [{ + checkLocalVariables: true + }] + }, { + code: '', + errors: [{message: 'Prop key for handleChange must begin with \'when\''}], + options: [{ + checkLocalVariables: true, + eventHandlerPrefix: 'handle', + eventHandlerPropPrefix: 'when' + }] }, { code: '', errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}] From 71c7d01efe41cd448518331fd72bd05bba565680 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 27 Nov 2019 23:04:50 -0800 Subject: [PATCH 09/36] [Tests] temporarily pin eslint 6 tests to v6.6 Pending https://github.com/eslint/eslint/issues/12614 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33f1c0ff16..c3ec49bfae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ env: global: - TEST=true matrix: - - ESLINT=6 + - ESLINT=6.6 - ESLINT=5 - ESLINT=4 after_success: @@ -27,13 +27,13 @@ matrix: fast_finish: true include: - node_js: 'lts/*' - env: PRETEST=true + env: PRETEST=true ESLINT=6.6 exclude: - node_js: '4' env: ESLINT=5 - node_js: '4' - env: ESLINT=6 + env: ESLINT=6.6 - node_js: '6' - env: ESLINT=6 + env: ESLINT=6.6 allow_failures: - node_js: '11' From 55b605f9ca6541a4c655d82b7595bb71b0ed552c Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 22 Nov 2019 21:28:46 -0500 Subject: [PATCH 10/36] [Fix] `sort-prop-types`, `jsx-sort-default-props`: disable broken autofix See #1940. --- docs/rules/jsx-sort-default-props.md | 2 - docs/rules/sort-prop-types.md | 3 - lib/rules/jsx-sort-default-props.js | 14 +- lib/rules/sort-prop-types.js | 38 +- tests/lib/rules/jsx-sort-default-props.js | 451 +++++----- tests/lib/rules/sort-prop-types.js | 998 +++++++++++----------- 6 files changed, 789 insertions(+), 717 deletions(-) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index 251726b9c2..d4b48f8181 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -2,8 +2,6 @@ Some developers prefer to sort `defaultProps` declarations alphabetically to be able to find necessary declarations easier at a later time. Others feel that it adds complexity and becomes a burden to maintain. -**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. - ## Rule Details This rule checks all components and verifies that all `defaultProps` declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive. diff --git a/docs/rules/sort-prop-types.md b/docs/rules/sort-prop-types.md index ac5798e3c2..6e19a1751c 100644 --- a/docs/rules/sort-prop-types.md +++ b/docs/rules/sort-prop-types.md @@ -2,9 +2,6 @@ Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain. -**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. - - ## Rule Details This rule checks all components and verifies that all propTypes declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive. diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index 76b881c161..88f230ae6d 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -8,7 +8,7 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); -const propTypesSortUtil = require('../util/propTypesSort'); +// const propTypesSortUtil = require('../util/propTypesSort'); // ------------------------------------------------------------------------------ // Rule Definition @@ -23,7 +23,7 @@ module.exports = { url: docsUrl('jsx-sort-default-props') }, - fixable: 'code', + // fixable: 'code', schema: [{ type: 'object', @@ -100,9 +100,9 @@ module.exports = { * @returns {void} */ function checkSorted(declarations) { - function fix(fixer) { - return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase); - } + // function fix(fixer) { + // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase); + // } declarations.reduce((prev, curr, idx, decls) => { if (/Spread(?:Property|Element)$/.test(curr.type)) { @@ -120,8 +120,8 @@ module.exports = { if (currentPropName < prevPropName) { context.report({ node: curr, - message: 'Default prop types declarations should be sorted alphabetically', - fix + message: 'Default prop types declarations should be sorted alphabetically' + // fix }); return prev; diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 6e0d3c99c9..9dbf13ad1a 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -8,7 +8,7 @@ const variableUtil = require('../util/variable'); const propsUtil = require('../util/props'); const docsUrl = require('../util/docsUrl'); const propWrapperUtil = require('../util/propWrapper'); -const propTypesSortUtil = require('../util/propTypesSort'); +// const propTypesSortUtil = require('../util/propTypesSort'); // ------------------------------------------------------------------------------ // Rule Definition @@ -23,7 +23,7 @@ module.exports = { url: docsUrl('sort-prop-types') }, - fixable: 'code', + // fixable: 'code', schema: [{ type: 'object', @@ -98,17 +98,17 @@ module.exports = { return; } - function fix(fixer) { - return propTypesSortUtil.fixPropTypesSort( - fixer, - context, - declarations, - ignoreCase, - requiredFirst, - callbacksLast, - sortShapeProp - ); - } + // function fix(fixer) { + // return propTypesSortUtil.fixPropTypesSort( + // fixer, + // context, + // declarations, + // ignoreCase, + // requiredFirst, + // callbacksLast, + // sortShapeProp + // ); + // } declarations.reduce((prev, curr, idx, decls) => { if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') { @@ -136,8 +136,8 @@ module.exports = { // Encountered a non-required prop after a required prop context.report({ node: curr, - message: 'Required prop types must be listed before all other prop types', - fix + message: 'Required prop types must be listed before all other prop types' + // fix }); return curr; } @@ -152,8 +152,8 @@ module.exports = { // Encountered a non-callback prop after a callback prop context.report({ node: prev, - message: 'Callback prop types must be listed after all other prop types', - fix + message: 'Callback prop types must be listed after all other prop types' + // fix }); return prev; } @@ -162,8 +162,8 @@ module.exports = { if (!noSortAlphabetically && currentPropName < prevPropName) { context.report({ node: curr, - message: 'Prop types declarations should be sorted alphabetically', - fix + message: 'Prop types declarations should be sorted alphabetically' + // fix }); return prev; } diff --git a/tests/lib/rules/jsx-sort-default-props.js b/tests/lib/rules/jsx-sort-default-props.js index 4f3918fb25..6a07cde0d0 100644 --- a/tests/lib/rules/jsx-sort-default-props.js +++ b/tests/lib/rules/jsx-sort-default-props.js @@ -374,24 +374,24 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 10, column: 5, type: 'Property' - }], - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' b: PropTypes.any,', - ' c: PropTypes.any', - ' };', - ' static defaultProps = {', - ' a: "a",', - ' b: "b",', - ' c: "c"', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' b: PropTypes.any,', + // ' c: PropTypes.any', + // ' };', + // ' static defaultProps = {', + // ' a: "a",', + // ' b: "b",', + // ' c: "c"', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -411,24 +411,24 @@ ruleTester.run('jsx-sort-default-props', rule, { '}' ].join('\n'), parser: parsers.BABEL_ESLINT, - errors: 2, - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' b: PropTypes.any,', - ' c: PropTypes.any', - ' };', - ' static defaultProps = {', - ' a: "a",', - ' b: "b",', - ' c: "c"', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + errors: 2 + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' b: PropTypes.any,', + // ' c: PropTypes.any', + // ' };', + // ' static defaultProps = {', + // ' a: "a",', + // ' b: "b",', + // ' c: "c"', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -454,22 +454,22 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 8, column: 5, type: 'Property' - }], - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' b: PropTypes.any', - ' };', - ' static defaultProps = {', - ' a: "a",', - ' Z: "Z",', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' b: PropTypes.any', + // ' };', + // ' static defaultProps = {', + // ' a: "a",', + // ' Z: "Z",', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -492,22 +492,22 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 8, column: 5, type: 'Property' - }], - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' z: PropTypes.any', - ' };', - ' static defaultProps = {', - ' Z: "Z",', - ' a: "a",', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' z: PropTypes.any', + // ' };', + // ' static defaultProps = {', + // ' Z: "Z",', + // ' a: "a",', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class Hello extends React.Component {', @@ -530,22 +530,22 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 12, column: 3, type: 'Property' - }], - output: [ - 'class Hello extends React.Component {', - ' render() {', - ' return
Hello
;', - ' }', - '}', - 'Hello.propTypes = {', - ' "a": PropTypes.string,', - ' "b": PropTypes.string', - '};', - 'Hello.defaultProps = {', - ' "a": "a",', - ' "b": "b"', - '};' - ].join('\n') + }] + // output: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "b": PropTypes.string', + // '};', + // 'Hello.defaultProps = {', + // ' "a": "a",', + // ' "b": "b"', + // '};' + // ].join('\n') }, { code: [ 'class Hello extends React.Component {', @@ -565,24 +565,24 @@ ruleTester.run('jsx-sort-default-props', rule, { '};' ].join('\n'), parser: parsers.BABEL_ESLINT, - errors: 2, - output: [ - 'class Hello extends React.Component {', - ' render() {', - ' return
Hello
;', - ' }', - '}', - 'Hello.propTypes = {', - ' "a": PropTypes.string,', - ' "b": PropTypes.string,', - ' "c": PropTypes.string', - '};', - 'Hello.defaultProps = {', - ' "a": "a",', - ' "b": "b",', - ' "c": "c"', - '};' - ].join('\n') + errors: 2 + // output: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "b": PropTypes.string,', + // ' "c": PropTypes.string', + // '};', + // 'Hello.defaultProps = {', + // ' "a": "a",', + // ' "b": "b",', + // ' "c": "c"', + // '};' + // ].join('\n') }, { code: [ 'class Hello extends React.Component {', @@ -605,23 +605,66 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 12, column: 3, type: 'Property' - }], - output: [ - 'class Hello extends React.Component {', - ' render() {', - ' return
Hello
;', - ' }', - '}', - 'Hello.propTypes = {', - ' "a": PropTypes.string,', - ' "B": PropTypes.string,', - '};', - 'Hello.defaultProps = {', - ' "B": "B",', - ' "a": "a",', - '};' - ].join('\n') + }] + // output: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "B": PropTypes.string,', + // '};', + // 'Hello.defaultProps = {', + // ' "B": "B",', + // ' "a": "a",', + // '};' + // ].join('\n') }, { + // Disabled test for comments -- fails + // code: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "B": PropTypes.string,', + // '};', + // 'Hello.defaultProps = {', + // ' /* a */', + // ' "a": "a",', + // ' /* B */', + // ' "B": "B",', + // '};' + // ].join('\n'), + // parser: parsers.BABEL_ESLINT, + // errors: [{ + // message: ERROR_MESSAGE, + // line: 14, + // column: 3, + // type: 'Property' + // }], + // output: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "B": PropTypes.string,', + // '};', + // 'Hello.defaultProps = {', + // ' /* B */', + // ' "B": "B",', + // ' /* a */', + // ' "a": "a",', + // '};' + // ].join('\n') + // }, { code: [ 'class Hello extends React.Component {', ' render() {', @@ -646,22 +689,22 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 12, column: 3, type: 'Property' - }], - output: [ - 'class Hello extends React.Component {', - ' render() {', - ' return
Hello
;', - ' }', - '}', - 'Hello.propTypes = {', - ' "a": PropTypes.string,', - ' "B": PropTypes.string,', - '};', - 'Hello.defaultProps = {', - ' "a": "a",', - ' "B": "B",', - '};' - ].join('\n') + }] + // output: [ + // 'class Hello extends React.Component {', + // ' render() {', + // ' return
Hello
;', + // ' }', + // '}', + // 'Hello.propTypes = {', + // ' "a": PropTypes.string,', + // ' "B": PropTypes.string,', + // '};', + // 'Hello.defaultProps = {', + // ' "a": "a",', + // ' "B": "B",', + // '};' + // ].join('\n') }, { code: [ 'const First = (props) =>
;', @@ -681,20 +724,20 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 8, column: 3, type: 'Property' - }], - output: [ - 'const First = (props) =>
;', - 'const propTypes = {', - ' z: PropTypes.string,', - ' a: PropTypes.any,', - '};', - 'const defaultProps = {', - ' a: "a",', - ' z: "z",', - '};', - 'First.propTypes = propTypes;', - 'First.defaultProps = defaultProps;' - ].join('\n') + }] + // output: [ + // 'const First = (props) =>
;', + // 'const propTypes = {', + // ' z: PropTypes.string,', + // ' a: PropTypes.any,', + // '};', + // 'const defaultProps = {', + // ' a: "a",', + // ' z: "z",', + // '};', + // 'First.propTypes = propTypes;', + // 'First.defaultProps = defaultProps;' + // ].join('\n') }, { code: [ 'export default class ClassWithSpreadInPropTypes extends BaseClass {', @@ -716,21 +759,21 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 9, column: 5, type: 'Property' - }], - output: [ - 'export default class ClassWithSpreadInPropTypes extends BaseClass {', - ' static propTypes = {', - ' b: PropTypes.string,', - ' ...c.propTypes,', - ' a: PropTypes.string', - ' }', - ' static defaultProps = {', - ' a: "a",', - ' b: "b",', - ' ...c.defaultProps', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + // ' static propTypes = {', + // ' b: PropTypes.string,', + // ' ...c.propTypes,', + // ' a: PropTypes.string', + // ' }', + // ' static defaultProps = {', + // ' a: "a",', + // ' b: "b",', + // ' ...c.defaultProps', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'export default class ClassWithSpreadInPropTypes extends BaseClass {', @@ -753,27 +796,27 @@ ruleTester.run('jsx-sort-default-props', rule, { '}' ].join('\n'), parser: parsers.BABEL_ESLINT, - errors: 2, - output: [ - 'export default class ClassWithSpreadInPropTypes extends BaseClass {', - ' static propTypes = {', - ' a: PropTypes.string,', - ' b: PropTypes.string,', - ' c: PropTypes.string,', - ' d: PropTypes.string,', - ' e: PropTypes.string,', - ' f: PropTypes.string', - ' }', - ' static defaultProps = {', - ' a: "a",', - ' b: "b",', - ' ...c.defaultProps,', - ' e: "e",', - ' f: "f",', - ' ...d.defaultProps', - ' }', - '}' - ].join('\n') + errors: 2 + // output: [ + // 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + // ' static propTypes = {', + // ' a: PropTypes.string,', + // ' b: PropTypes.string,', + // ' c: PropTypes.string,', + // ' d: PropTypes.string,', + // ' e: PropTypes.string,', + // ' f: PropTypes.string', + // ' }', + // ' static defaultProps = {', + // ' a: "a",', + // ' b: "b",', + // ' ...c.defaultProps,', + // ' e: "e",', + // ' f: "f",', + // ' ...d.defaultProps', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'const defaults = {', @@ -800,25 +843,25 @@ ruleTester.run('jsx-sort-default-props', rule, { line: 15, column: 3, type: 'Property' - }], - output: [ - 'const defaults = {', - ' b: "b"', - '};', - 'const types = {', - ' a: PropTypes.string,', - ' b: PropTypes.string,', - ' c: PropTypes.string', - '};', - 'function StatelessComponentWithSpreadInPropTypes({ a, b, c }) {', - ' return
{a}{b}{c}
;', - '}', - 'StatelessComponentWithSpreadInPropTypes.propTypes = types;', - 'StatelessComponentWithSpreadInPropTypes.defaultProps = {', - ' a: "a",', - ' c: "c",', - ' ...defaults,', - '};' - ].join('\n') + }] + // output: [ + // 'const defaults = {', + // ' b: "b"', + // '};', + // 'const types = {', + // ' a: PropTypes.string,', + // ' b: PropTypes.string,', + // ' c: PropTypes.string', + // '};', + // 'function StatelessComponentWithSpreadInPropTypes({ a, b, c }) {', + // ' return
{a}{b}{c}
;', + // '}', + // 'StatelessComponentWithSpreadInPropTypes.propTypes = types;', + // 'StatelessComponentWithSpreadInPropTypes.defaultProps = {', + // ' a: "a",', + // ' c: "c",', + // ' ...defaults,', + // '};' + // ].join('\n') }] }); diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js index 6326a7d89d..65a529d589 100644 --- a/tests/lib/rules/sort-prop-types.js +++ b/tests/lib/rules/sort-prop-types.js @@ -486,18 +486,52 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') + }, { + code: [ 'var First = createReactClass({', ' propTypes: {', - ' a: PropTypes.any,', - ' z: PropTypes.string', + ' /* z */', + ' z: PropTypes.string,', + ' /* a */', + ' a: PropTypes.any', ' },', ' render: function() {', ' return
;', ' }', '});' - ].join('\n') + ].join('\n'), + errors: [{ + message: ERROR_MESSAGE, + line: 6, + column: 5, + type: 'Property' + }] + // Disabled test for comments -- fails + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' /* a */', + // ' a: PropTypes.any,', + // ' /* z */', + // ' z: PropTypes.string', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -515,18 +549,18 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' Z: PropTypes.any,', - ' z: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' Z: PropTypes.any,', + // ' z: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -547,18 +581,18 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any,', - ' Z: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' Z: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -573,20 +607,20 @@ ruleTester.run('sort-prop-types', rule, { ' }', '});' ].join('\n'), - errors: 2, - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' A: PropTypes.any,', - ' Z: PropTypes.string,', - ' a: PropTypes.any,', - ' z: PropTypes.string', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + errors: 2 + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' A: PropTypes.any,', + // ' Z: PropTypes.string,', + // ' a: PropTypes.any,', + // ' z: PropTypes.string', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -608,27 +642,27 @@ ruleTester.run('sort-prop-types', rule, { ' }', '});' ].join('\n'), - errors: 2, - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' Zz: PropTypes.string,', - ' a: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});', - 'var Second = createReactClass({', - ' propTypes: {', - ' ZZ: PropTypes.string,', - ' aAA: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + errors: 2 + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' Zz: PropTypes.string,', + // ' a: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});', + // 'var Second = createReactClass({', + // ' propTypes: {', + // ' ZZ: PropTypes.string,', + // ' aAA: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'class First extends React.Component {', @@ -650,27 +684,27 @@ ruleTester.run('sort-prop-types', rule, { ' ZZ: PropTypes.string', '};' ].join('\n'), - errors: 2, - output: [ - 'class First extends React.Component {', - ' render() {', - ' return
;', - ' }', - '}', - 'First.propTypes = {', - ' bb: PropTypes.string,', - ' yy: PropTypes.any', - '};', - 'class Second extends React.Component {', - ' render() {', - ' return
;', - ' }', - '}', - 'Second.propTypes = {', - ' ZZ: PropTypes.string,', - ' aAA: PropTypes.any', - '};' - ].join('\n') + errors: 2 + // output: [ + // 'class First extends React.Component {', + // ' render() {', + // ' return
;', + // ' }', + // '}', + // 'First.propTypes = {', + // ' bb: PropTypes.string,', + // ' yy: PropTypes.any', + // '};', + // 'class Second extends React.Component {', + // ' render() {', + // ' return
;', + // ' }', + // '}', + // 'Second.propTypes = {', + // ' ZZ: PropTypes.string,', + // ' aAA: PropTypes.any', + // '};' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -685,19 +719,19 @@ ruleTester.run('sort-prop-types', rule, { '}' ].join('\n'), parser: parsers.BABEL_ESLINT, - errors: 2, - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' y: PropTypes.any,', - ' z: PropTypes.any', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + errors: 2 + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' y: PropTypes.any,', + // ' z: PropTypes.any', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -715,19 +749,19 @@ ruleTester.run('sort-prop-types', rule, { settings: { propWrapperFunctions: ['forbidExtraProps'] }, - errors: 2, - output: [ - 'class Component extends React.Component {', - ' static propTypes = forbidExtraProps({', - ' a: PropTypes.any,', - ' y: PropTypes.any,', - ' z: PropTypes.any', - ' });', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + errors: 2 + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = forbidExtraProps({', + // ' a: PropTypes.any,', + // ' y: PropTypes.any,', + // ' z: PropTypes.any', + // ' });', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -750,20 +784,20 @@ ruleTester.run('sort-prop-types', rule, { line: 6, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - ' onBar: PropTypes.func,', - ' onFoo: PropTypes.func', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // ' onBar: PropTypes.func,', + // ' onFoo: PropTypes.func', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'class Component extends React.Component {', @@ -787,20 +821,20 @@ ruleTester.run('sort-prop-types', rule, { line: 6, column: 5, type: 'Property' - }], - output: [ - 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - ' onBar: PropTypes.func,', - ' onFoo: PropTypes.func', - ' };', - ' render() {', - ' return
;', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'class Component extends React.Component {', + // ' static propTypes = {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // ' onBar: PropTypes.func,', + // ' onFoo: PropTypes.func', + // ' };', + // ' render() {', + // ' return
;', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'class First extends React.Component {', @@ -823,20 +857,20 @@ ruleTester.run('sort-prop-types', rule, { line: 10, column: 5, type: 'Property' - }], - output: [ - 'class First extends React.Component {', - ' render() {', - ' return
;', - ' }', - '}', - 'First.propTypes = {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - ' onBar: PropTypes.func,', - ' onFoo: PropTypes.func', - '};' - ].join('\n') + }] + // output: [ + // 'class First extends React.Component {', + // ' render() {', + // ' return
;', + // ' }', + // '}', + // 'First.propTypes = {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // ' onBar: PropTypes.func,', + // ' onFoo: PropTypes.func', + // '};' + // ].join('\n') }, { code: [ 'class First extends React.Component {', @@ -862,20 +896,20 @@ ruleTester.run('sort-prop-types', rule, { line: 10, column: 5, type: 'Property' - }], - output: [ - 'class First extends React.Component {', - ' render() {', - ' return
;', - ' }', - '}', - 'First.propTypes = forbidExtraProps({', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - ' onBar: PropTypes.func,', - ' onFoo: PropTypes.func', - '});' - ].join('\n') + }] + // output: [ + // 'class First extends React.Component {', + // ' render() {', + // ' return
;', + // ' }', + // '}', + // 'First.propTypes = forbidExtraProps({', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // ' onBar: PropTypes.func,', + // ' onFoo: PropTypes.func', + // '});' + // ].join('\n') }, { code: [ 'const First = (props) =>
;', @@ -893,15 +927,15 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'const First = (props) =>
;', - 'const propTypes = {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - '};', - 'First.propTypes = forbidExtraProps(propTypes);' - ].join('\n') + }] + // output: [ + // 'const First = (props) =>
;', + // 'const propTypes = {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // '};', + // 'First.propTypes = forbidExtraProps(propTypes);' + // ].join('\n') }, { code: [ 'const First = (props) =>
;', @@ -919,15 +953,15 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'const First = (props) =>
;', - 'const propTypes = {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - '};', - 'First.propTypes = propTypes;' - ].join('\n') + }] + // output: [ + // 'const First = (props) =>
;', + // 'const propTypes = {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // '};', + // 'First.propTypes = propTypes;' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -950,20 +984,20 @@ ruleTester.run('sort-prop-types', rule, { line: 5, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any,', - ' z: PropTypes.string,', - ' onBar: PropTypes.func,', - ' onFoo: PropTypes.func', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string,', + // ' onBar: PropTypes.func,', + // ' onFoo: PropTypes.func', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -985,19 +1019,19 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' barRequired: PropTypes.string.isRequired,', - ' fooRequired: PropTypes.string.isRequired,', - ' a: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' barRequired: PropTypes.string.isRequired,', + // ' fooRequired: PropTypes.string.isRequired,', + // ' a: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -1019,19 +1053,19 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' barRequired: PropTypes.string.isRequired,', - ' a: PropTypes.any,', - ' onFoo: PropTypes.func', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' barRequired: PropTypes.string.isRequired,', + // ' a: PropTypes.any,', + // ' onFoo: PropTypes.func', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'export default class ClassWithSpreadInPropTypes extends BaseClass {', @@ -1049,17 +1083,17 @@ ruleTester.run('sort-prop-types', rule, { line: 6, column: 5, type: 'Property' - }], - output: [ - 'export default class ClassWithSpreadInPropTypes extends BaseClass {', - ' static propTypes = {', - ' b: PropTypes.string,', - ' ...a.propTypes,', - ' c: PropTypes.string,', - ' d: PropTypes.string', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + // ' static propTypes = {', + // ' b: PropTypes.string,', + // ' ...a.propTypes,', + // ' c: PropTypes.string,', + // ' d: PropTypes.string', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'export default class ClassWithSpreadInPropTypes extends BaseClass {', @@ -1079,19 +1113,19 @@ ruleTester.run('sort-prop-types', rule, { line: 6, column: 5, type: 'Property' - }], - output: [ - 'export default class ClassWithSpreadInPropTypes extends BaseClass {', - ' static propTypes = {', - ' b: PropTypes.string,', - ' ...a.propTypes,', - ' d: PropTypes.string,', - ' f: PropTypes.string,', - ' ...e.propTypes,', - ' c: PropTypes.string', - ' }', - '}' - ].join('\n') + }] + // output: [ + // 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + // ' static propTypes = {', + // ' b: PropTypes.string,', + // ' ...a.propTypes,', + // ' d: PropTypes.string,', + // ' f: PropTypes.string,', + // ' ...e.propTypes,', + // ' c: PropTypes.string', + // ' }', + // '}' + // ].join('\n') }, { code: [ 'const propTypes = {', @@ -1108,17 +1142,17 @@ ruleTester.run('sort-prop-types', rule, { line: 3, column: 3, type: 'Property' - }], - output: [ - 'const propTypes = {', - ' a: PropTypes.string,', - ' b: PropTypes.string,', - '};', - 'const TextFieldLabel = (props) => {', - ' return
;', - '};', - 'TextFieldLabel.propTypes = propTypes;' - ].join('\n') + }] + // output: [ + // 'const propTypes = {', + // ' a: PropTypes.string,', + // ' b: PropTypes.string,', + // '};', + // 'const TextFieldLabel = (props) => {', + // ' return
;', + // '};', + // 'TextFieldLabel.propTypes = propTypes;' + // ].join('\n') }, { code: ` class Component extends React.Component { @@ -1149,23 +1183,23 @@ ruleTester.run('sort-prop-types', rule, { line: 13, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape({ - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - }), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape({ + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // }), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1187,19 +1221,19 @@ ruleTester.run('sort-prop-types', rule, { line: 10, column: 9, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape(), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape(), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1221,19 +1255,19 @@ ruleTester.run('sort-prop-types', rule, { line: 10, column: 9, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape(someType), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape(someType), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1280,24 +1314,24 @@ ruleTester.run('sort-prop-types', rule, { line: 14, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - a: PropTypes.shape({ - C: PropTypes.string, - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - }), - y: PropTypes.any, - z: PropTypes.any, - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // a: PropTypes.shape({ + // C: PropTypes.string, + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // }), + // y: PropTypes.any, + // z: PropTypes.any, + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1330,24 +1364,24 @@ ruleTester.run('sort-prop-types', rule, { line: 14, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape({ - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - C: PropTypes.string, - }), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape({ + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // C: PropTypes.string, + // }), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1375,24 +1409,24 @@ ruleTester.run('sort-prop-types', rule, { line: 12, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape({ - c: PropTypes.number.isRequired, - a: PropTypes.string, - b: PropTypes.any, - d: PropTypes.bool, - }), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape({ + // c: PropTypes.number.isRequired, + // a: PropTypes.string, + // b: PropTypes.any, + // d: PropTypes.bool, + // }), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1426,25 +1460,25 @@ ruleTester.run('sort-prop-types', rule, { line: 14, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape({ - a: PropTypes.string, - b: PropTypes.any, - c: PropTypes.number.isRequired, - d: PropTypes.bool, - onFoo: PropTypes.func, - }), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape({ + // a: PropTypes.string, + // b: PropTypes.any, + // c: PropTypes.number.isRequired, + // d: PropTypes.bool, + // onFoo: PropTypes.func, + // }), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1478,26 +1512,26 @@ ruleTester.run('sort-prop-types', rule, { line: 16, column: 11, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.any, - y: PropTypes.any, - z: PropTypes.shape({ - a: PropTypes.string, - b: PropTypes.any, - c: PropTypes.number.isRequired, - ...otherPropTypes, - d: PropTypes.string, - f: PropTypes.bool, - }), - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.any, + // y: PropTypes.any, + // z: PropTypes.shape({ + // a: PropTypes.string, + // b: PropTypes.any, + // c: PropTypes.number.isRequired, + // ...otherPropTypes, + // d: PropTypes.string, + // f: PropTypes.bool, + // }), + // }; + // ` }, { code: ` class Component extends React.Component { @@ -1539,23 +1573,23 @@ ruleTester.run('sort-prop-types', rule, { line: 9, column: 13, type: 'Property' - }], - output: ` - class Component extends React.Component { - static propTypes = { - a: PropTypes.shape({ - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - }), - y: PropTypes.any, - z: PropTypes.any, - }; - render() { - return
; - } - } - ` + }] + // output: ` + // class Component extends React.Component { + // static propTypes = { + // a: PropTypes.shape({ + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // }), + // y: PropTypes.any, + // z: PropTypes.any, + // }; + // render() { + // return
; + // } + // } + // ` }, { code: [ 'var First = createReactClass({', @@ -1576,18 +1610,18 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any,', - ' z: PropTypes.string', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' z: PropTypes.string', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: [ 'var First = createReactClass({', @@ -1609,19 +1643,19 @@ ruleTester.run('sort-prop-types', rule, { line: 4, column: 5, type: 'Property' - }], - output: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any,', - ' \'data-letter\': PropTypes.string,', - ' e: PropTypes.any', - ' },', - ' render: function() {', - ' return
;', - ' }', - '});' - ].join('\n') + }] + // output: [ + // 'var First = createReactClass({', + // ' propTypes: {', + // ' a: PropTypes.any,', + // ' \'data-letter\': PropTypes.string,', + // ' e: PropTypes.any', + // ' },', + // ' render: function() {', + // ' return
;', + // ' }', + // '});' + // ].join('\n') }, { code: ` class Component extends React.Component { @@ -1642,18 +1676,18 @@ ruleTester.run('sort-prop-types', rule, { line: 9, column: 9, type: 'Property' - }], - output: ` - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - 0: PropTypes.any, - 1: PropTypes.any, - }; - ` + }] + // output: ` + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // 0: PropTypes.any, + // 1: PropTypes.any, + // }; + // ` }, { code: ` const shape = { @@ -1685,23 +1719,23 @@ ruleTester.run('sort-prop-types', rule, { line: 5, column: 9, type: 'Property' - }], - output: ` - const shape = { - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - }; - class Component extends React.Component { - static propTypes = { - x: PropTypes.shape(shape), - }; + }] + // output: ` + // const shape = { + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // }; + // class Component extends React.Component { + // static propTypes = { + // x: PropTypes.shape(shape), + // }; - render() { - return
; - } - } - ` + // render() { + // return
; + // } + // } + // ` }, { code: ` const shape = { @@ -1731,21 +1765,21 @@ ruleTester.run('sort-prop-types', rule, { line: 5, column: 9, type: 'Property' - }], - output: ` - const shape = { - a: PropTypes.any, - b: PropTypes.bool, - c: PropTypes.any, - }; - class Component extends React.Component { - render() { - return
; - } - } - Component.propTypes = { - x: PropTypes.shape(shape) - }; - ` + }] + // output: ` + // const shape = { + // a: PropTypes.any, + // b: PropTypes.bool, + // c: PropTypes.any, + // }; + // class Component extends React.Component { + // render() { + // return
; + // } + // } + // Component.propTypes = { + // x: PropTypes.shape(shape) + // }; + // ` }] }); From 87d4cabe8d271b4146436ac15197b13fd67c7a62 Mon Sep 17 00:00:00 2001 From: jeffjing Date: Thu, 21 Nov 2019 11:23:43 +0800 Subject: [PATCH 11/36] [Docs] `jsx-first-prop-new-line`: fix wrong rule name --- docs/rules/jsx-first-prop-new-line.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/jsx-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md index e414d953a4..0d9edabf51 100644 --- a/docs/rules/jsx-first-prop-new-line.md +++ b/docs/rules/jsx-first-prop-new-line.md @@ -102,7 +102,7 @@ The following patterns are **not** considered warnings when configured `"multili ## Rule Options ...jsx -"react/jsx-max-props-per-line": `"always" | "never" | "multiline" | "multiline-multiprop"` +"react/jsx-first-prop-new-line": `"always" | "never" | "multiline" | "multiline-multiprop"` ... ## When not to use From 62df1dd316df39d7aaed02d6bacd415803301943 Mon Sep 17 00:00:00 2001 From: Chris Nickel Date: Fri, 22 Nov 2019 10:30:43 -0500 Subject: [PATCH 12/36] [Docs] `prop-types`: Update 'skipUndeclared' in rule options - Updated description to indicate it's of type boolean. - Included 'skipUndeclared' in rule option's snippet. --- docs/rules/prop-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/prop-types.md b/docs/rules/prop-types.md index 627b93bb94..7dea535f04 100644 --- a/docs/rules/prop-types.md +++ b/docs/rules/prop-types.md @@ -102,14 +102,14 @@ This rule can take one argument to ignore some specific props during validation. ```js ... -"react/prop-types": [, { ignore: , customValidators: }] +"react/prop-types": [, { ignore: , customValidators: , skipUndeclared: }] ... ``` * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. * `ignore`: optional array of props name to ignore during validation. * `customValidators`: optional array of validators used for propTypes validation. -* `skipUndeclared`: only error on components that have a propTypes block declared +* `skipUndeclared`: optional boolean to only error on components that have a propTypes block declared. ### As for "exceptions" From 225b433b21528b3c196626b0e56bfebad4513193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Go=C5=9Bcicki?= Date: Thu, 7 Nov 2019 15:18:05 +0100 Subject: [PATCH 13/36] [Docs] `jsx-first-prop-new-line`: Fix documentation formatting --- docs/rules/jsx-first-prop-new-line.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rules/jsx-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md index 0d9edabf51..e40a4a2dc9 100644 --- a/docs/rules/jsx-first-prop-new-line.md +++ b/docs/rules/jsx-first-prop-new-line.md @@ -101,9 +101,9 @@ The following patterns are **not** considered warnings when configured `"multili ## Rule Options -...jsx -"react/jsx-first-prop-new-line": `"always" | "never" | "multiline" | "multiline-multiprop"` -... +```jsx +"react/jsx-first-props-per-line": `"always" | "never" | "multiline" | "multiline-multiprop"` +``` ## When not to use From 83ac4c0b6f777cf46082a17c6c85435eb831e4f6 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Fri, 1 Nov 2019 23:24:26 +0800 Subject: [PATCH 14/36] [eslint] fix func-names and change object-shorthand to 'always' --- .eslintrc | 1 + lib/rules/boolean-prop-naming.js | 3 +- lib/rules/default-props-match-prop-types.js | 2 +- lib/rules/display-name.js | 2 +- lib/rules/jsx-closing-bracket-location.js | 2 +- lib/rules/jsx-filename-extension.js | 2 +- lib/rules/jsx-fragments.js | 6 ++-- lib/rules/jsx-indent.js | 2 +- lib/rules/jsx-max-props-per-line.js | 2 +- lib/rules/jsx-sort-props.js | 6 ++-- lib/rules/no-array-index-key.js | 2 +- lib/rules/no-direct-mutation-state.js | 6 ++-- lib/rules/no-multi-comp.js | 2 +- lib/rules/no-set-state.js | 2 +- lib/rules/no-unescaped-entities.js | 2 +- lib/rules/no-unused-prop-types.js | 2 +- lib/rules/no-unused-state.js | 10 +++---- lib/rules/prefer-read-only-props.js | 2 +- lib/rules/prefer-stateless-function.js | 10 +++---- lib/rules/prop-types.js | 2 +- lib/rules/require-default-props.js | 2 +- lib/rules/require-optimization.js | 30 +++++++++---------- lib/rules/require-render-return.js | 2 +- lib/rules/sort-comp.js | 2 +- lib/util/Components.js | 2 +- lib/util/propTypes.js | 4 +-- lib/util/usedPropTypes.js | 2 +- .../lib/rules/jsx-closing-bracket-location.js | 4 +-- 28 files changed, 59 insertions(+), 57 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4d2d2a26fb..4a1be50416 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,6 +16,7 @@ "rules": { "comma-dangle": [2, "never"], "object-curly-spacing": [2, "never"], + "object-shorthand": [2, "always"], "array-bracket-spacing": [2, "never"], "max-len": [2, 120, { "ignoreStrings": true, diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index aba753f866..e319df8d88 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -282,7 +282,8 @@ module.exports = { } }, - 'Program:exit': function () { + // eslint-disable-next-line object-shorthand + 'Program:exit'() { if (!rule) { return; } diff --git a/lib/rules/default-props-match-prop-types.js b/lib/rules/default-props-match-prop-types.js index 19d47a0e65..988949bc42 100644 --- a/lib/rules/default-props-match-prop-types.js +++ b/lib/rules/default-props-match-prop-types.js @@ -80,7 +80,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); // If no defaultProps could be found, we don't report anything. diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index 3c5c847110..0e0b4e7737 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -227,7 +227,7 @@ module.exports = { } }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); // Report missing display name for all components Object.keys(list).filter(component => !list[component].hasDisplayName).forEach((component) => { diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index a84bdfd699..e0863f9e81 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -236,7 +236,7 @@ module.exports = { lastAttributeNode[getOpeningElementId(node.parent)] = node; }, - 'JSXOpeningElement:exit': function (node) { + 'JSXOpeningElement:exit'(node) { const attributeNode = lastAttributeNode[getOpeningElementId(node)]; const cachedLastAttributeEndPos = attributeNode ? attributeNode.range[1] : null; let expectedNextLine; diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js index d636b068ae..3d23cb4dc6 100644 --- a/lib/rules/jsx-filename-extension.js +++ b/lib/rules/jsx-filename-extension.js @@ -80,7 +80,7 @@ module.exports = { JSXElement: handleJSX, JSXFragment: handleJSX, - 'Program:exit': function () { + 'Program:exit'() { if (!invalidNode) { return; } diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js index 353446c37d..d2ec3ab3c7 100644 --- a/lib/rules/jsx-fragments.js +++ b/lib/rules/jsx-fragments.js @@ -58,7 +58,7 @@ module.exports = { function getFixerToLong(jsxFragment) { const sourceCode = context.getSourceCode(); - return function (fixer) { + return function fix(fixer) { let source = sourceCode.getText(); source = replaceNode(source, jsxFragment.closingFragment, closeFragLong); source = replaceNode(source, jsxFragment.openingFragment, openFragLong); @@ -71,7 +71,7 @@ module.exports = { function getFixerToShort(jsxElement) { const sourceCode = context.getSourceCode(); - return function (fixer) { + return function fix(fixer) { let source = sourceCode.getText(); let lengthDiff; if (jsxElement.closingElement) { @@ -164,7 +164,7 @@ module.exports = { } }, - 'Program:exit': function () { + 'Program:exit'() { jsxElements.forEach((node) => { const openingEl = node.openingElement; const elName = elementType(openingEl); diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index f6dfc3ab92..fa81f8f673 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -95,7 +95,7 @@ module.exports = { * @private */ function getFixerFunction(node, needed) { - return function (fixer) { + return function fix(fixer) { const indent = Array(needed + 1).join(indentChar); return fixer.replaceTextRange( [node.range[0] - node.loc.start.column, node.range[0]], diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js index 06dad9d147..387a9ff0c8 100644 --- a/lib/rules/jsx-max-props-per-line.js +++ b/lib/rules/jsx-max-props-per-line.js @@ -62,7 +62,7 @@ module.exports = { }, '')); } const code = output.join('\n'); - return function (fixer) { + return function fix(fixer) { return fixer.replaceTextRange([front, back], code); }; } diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js index aa9a598f85..5b6265adb6 100644 --- a/lib/rules/jsx-sort-props.js +++ b/lib/rules/jsx-sort-props.js @@ -132,7 +132,7 @@ const generateFixerFunction = (node, context, reservedList) => { .slice(0) .map(group => group.slice(0).sort((a, b) => contextCompare(a, b, options))); - return function (fixer) { + return function fixFunction(fixer) { const fixers = []; let source = sourceCode.getText(); @@ -178,7 +178,7 @@ function validateReservedFirstConfig(context, reservedFirst) { )); if (reservedFirst.length === 0) { - return function (decl) { + return function report(decl) { context.report({ node: decl, message: 'A customized reserved first list must not be empty' @@ -186,7 +186,7 @@ function validateReservedFirstConfig(context, reservedFirst) { }; } if (nonReservedWords.length > 0) { - return function (decl) { + return function report(decl) { context.report({ node: decl, message: 'A customized reserved first list must only contain a subset of React reserved props.' + diff --git a/lib/rules/no-array-index-key.js b/lib/rules/no-array-index-key.js index d3cbddd2d9..0a81bc8100 100644 --- a/lib/rules/no-array-index-key.js +++ b/lib/rules/no-array-index-key.js @@ -213,7 +213,7 @@ module.exports = { checkPropValue(value.expression); }, - 'CallExpression:exit': function (node) { + 'CallExpression:exit'(node) { const mapIndexParamName = getMapIndexParamName(node); if (!mapIndexParamName) { return; diff --git a/lib/rules/no-direct-mutation-state.js b/lib/rules/no-direct-mutation-state.js index 30a88cfbff..816fbeb3d2 100644 --- a/lib/rules/no-direct-mutation-state.js +++ b/lib/rules/no-direct-mutation-state.js @@ -119,13 +119,13 @@ module.exports = { } }, - 'CallExpression:exit': function (node) { + 'CallExpression:exit'(node) { components.set(node, { inCallExpression: false }); }, - 'MethodDefinition:exit': function (node) { + 'MethodDefinition:exit'(node) { if (node.kind === 'constructor') { components.set(node, { inConstructor: false @@ -133,7 +133,7 @@ module.exports = { } }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).forEach((key) => { diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index a86662be58..12c5bf8359 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -58,7 +58,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + 'Program:exit'() { if (components.length() <= 1) { return; } diff --git a/lib/rules/no-set-state.js b/lib/rules/no-set-state.js index 7af70116f8..7e0c67fe9a 100644 --- a/lib/rules/no-set-state.js +++ b/lib/rules/no-set-state.js @@ -72,7 +72,7 @@ module.exports = { }); }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).filter(component => !isValid(list[component])).forEach((component) => { reportSetStateUsages(list[component]); diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js index b8f5e76e38..e42e140a68 100644 --- a/lib/rules/no-unescaped-entities.js +++ b/lib/rules/no-unescaped-entities.js @@ -109,7 +109,7 @@ module.exports = { } return { - 'Literal, JSXText': function (node) { + 'Literal, JSXText'(node) { if (jsxUtil.isJSX(node.parent)) { reportInvalidEntity(node); } diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index f7d9074656..5f675f6dcf 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -131,7 +131,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); // Report undeclared proptypes for all classes Object.keys(list).filter(component => mustBeValidated(list[component])).forEach((component) => { diff --git a/lib/rules/no-unused-state.js b/lib/rules/no-unused-state.js index fcdc749810..8eca5be07f 100644 --- a/lib/rules/no-unused-state.js +++ b/lib/rules/no-unused-state.js @@ -231,7 +231,7 @@ module.exports = { } }, - 'ObjectExpression:exit': function (node) { + 'ObjectExpression:exit'(node) { if (!classInfo) { return; } @@ -242,7 +242,7 @@ module.exports = { } }, - 'ClassDeclaration:exit': function () { + 'ClassDeclaration:exit'() { if (!classInfo) { return; } @@ -306,7 +306,7 @@ module.exports = { } }, - 'ClassProperty:exit': function (node) { + 'ClassProperty:exit'(node) { if ( classInfo && !node.static && @@ -326,7 +326,7 @@ module.exports = { classInfo.aliases = new Set(); }, - 'MethodDefinition:exit': function () { + 'MethodDefinition:exit'() { if (!classInfo) { return; } @@ -422,7 +422,7 @@ module.exports = { } }, - 'ExperimentalSpreadProperty, SpreadElement': function (node) { + 'ExperimentalSpreadProperty, SpreadElement'(node) { if (classInfo && isStateReference(node.argument)) { classInfo = null; } diff --git a/lib/rules/prefer-read-only-props.js b/lib/rules/prefer-read-only-props.js index 225dac0b27..8349435f24 100644 --- a/lib/rules/prefer-read-only-props.js +++ b/lib/rules/prefer-read-only-props.js @@ -33,7 +33,7 @@ module.exports = { }, create: Components.detect((context, components) => ({ - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).forEach((key) => { diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index ce5637399e..ee7e6a9897 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -196,21 +196,21 @@ module.exports = { * Mark component as pure as declared * @param {ASTNode} node The AST node being checked. */ - const markSCUAsDeclared = function (node) { + function markSCUAsDeclared(node) { components.set(node, { hasSCU: true }); - }; + } /** * Mark childContextTypes as declared * @param {ASTNode} node The AST node being checked. */ - const markChildContextTypesAsDeclared = function (node) { + function markChildContextTypesAsDeclared(node) { components.set(node, { hasChildContextTypes: true }); - }; + } /** * Mark a setState as used @@ -351,7 +351,7 @@ module.exports = { markReturnAsInvalid(node); }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).forEach((component) => { if ( diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index aa53691567..a97483371c 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -183,7 +183,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); // Report undeclared proptypes for all classes Object.keys(list).filter(component => mustBeValidated(list[component])).forEach((component) => { diff --git a/lib/rules/require-default-props.js b/lib/rules/require-default-props.js index 12a1515439..1a1544c14f 100644 --- a/lib/rules/require-default-props.js +++ b/lib/rules/require-default-props.js @@ -80,7 +80,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).filter(component => list[component].declaredPropTypes).forEach((component) => { diff --git a/lib/rules/require-optimization.js b/lib/rules/require-optimization.js index 40fe28e403..ee56e82b8e 100644 --- a/lib/rules/require-optimization.js +++ b/lib/rules/require-optimization.js @@ -41,7 +41,7 @@ module.exports = { * @param {ASTNode} node The AST node being checked. * @returns {Boolean} True if node is decorated with a PureRenderMixin, false if not. */ - const hasPureRenderDecorator = function (node) { + function hasPureRenderDecorator(node) { if (node.decorators && node.decorators.length) { for (let i = 0, l = node.decorators.length; i < l; i++) { if ( @@ -61,14 +61,14 @@ module.exports = { } return false; - }; + } /** * Checks to see if our component is custom decorated * @param {ASTNode} node The AST node being checked. * @returns {Boolean} True if node is decorated name with a custom decorated, false if not. */ - const hasCustomDecorator = function (node) { + function hasCustomDecorator(node) { const allowLength = allowDecorators.length; if (allowLength && node.decorators && node.decorators.length) { @@ -85,26 +85,26 @@ module.exports = { } return false; - }; + } /** * Checks if we are declaring a shouldComponentUpdate method * @param {ASTNode} node The AST node being checked. * @returns {Boolean} True if we are declaring a shouldComponentUpdate method, false if not. */ - const isSCUDeclared = function (node) { + function isSCUDeclared(node) { return Boolean( node && node.name === 'shouldComponentUpdate' ); - }; + } /** * Checks if we are declaring a PureRenderMixin mixin * @param {ASTNode} node The AST node being checked. * @returns {Boolean} True if we are declaring a PureRenderMixin method, false if not. */ - const isPureRenderDeclared = function (node) { + function isPureRenderDeclared(node) { let hasPR = false; if (node.value && node.value.elements) { for (let i = 0, l = node.value.elements.length; i < l; i++) { @@ -120,23 +120,23 @@ module.exports = { node.key.name === 'mixins' && hasPR ); - }; + } /** * Mark shouldComponentUpdate as declared * @param {ASTNode} node The AST node being checked. */ - const markSCUAsDeclared = function (node) { + function markSCUAsDeclared(node) { components.set(node, { hasSCU: true }); - }; + } /** * Reports missing optimization for a given component * @param {Object} component The component to process */ - const reportMissingOptimization = function (component) { + function reportMissingOptimization(component) { context.report({ node: component.node, message: MISSING_MESSAGE, @@ -144,13 +144,13 @@ module.exports = { component: component.name } }); - }; + } /** * Checks if we are declaring function in class * @returns {Boolean} True if we are declaring function in class, false if not. */ - const isFunctionInClass = function () { + function isFunctionInClass() { let blockNode; let scope = context.getScope(); while (scope) { @@ -162,7 +162,7 @@ module.exports = { } return false; - }; + } return { ArrowFunctionExpression(node) { @@ -217,7 +217,7 @@ module.exports = { } }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); // Report missing shouldComponentUpdate for all components diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js index c659e1f50d..72a22490e0 100644 --- a/lib/rules/require-render-return.js +++ b/lib/rules/require-render-return.js @@ -72,7 +72,7 @@ module.exports = { markReturnStatementPresent(node); }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).forEach((component) => { if ( diff --git a/lib/rules/sort-comp.js b/lib/rules/sort-comp.js index 752a53bd38..67efdec716 100644 --- a/lib/rules/sort-comp.js +++ b/lib/rules/sort-comp.js @@ -427,7 +427,7 @@ module.exports = { } return { - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).forEach((component) => { const properties = astUtil.getComponentProperties(list[component].node); diff --git a/lib/util/Components.js b/lib/util/Components.js index dd3c88ffd7..c4334c8c0f 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -875,7 +875,7 @@ function componentRule(rule, context) { )); allKeys.forEach((instruction) => { - updatedRuleInstructions[instruction] = function (node) { + updatedRuleInstructions[instruction] = (node) => { if (instruction in detectionInstructions) { detectionInstructions[instruction](node); } diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index c60f92c767..dfb2a7efd0 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -758,11 +758,11 @@ module.exports = function propTypesInstructions(context, components, utils) { stack.push(Object.create(typeScope())); }, - 'BlockStatement:exit': function () { + 'BlockStatement:exit'() { stack.pop(); }, - 'Program:exit': function () { + 'Program:exit'() { classExpressions.forEach((node) => { if (isSuperTypeParameterPropsDeclaration(node)) { markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); diff --git a/lib/util/usedPropTypes.js b/lib/util/usedPropTypes.js index 992ab58077..478100833f 100755 --- a/lib/util/usedPropTypes.js +++ b/lib/util/usedPropTypes.js @@ -527,7 +527,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils) } }, - 'Program:exit': function () { + 'Program:exit'() { const list = components.list(); Object.keys(list).filter(component => mustBeValidated(list[component])).forEach((component) => { diff --git a/tests/lib/rules/jsx-closing-bracket-location.js b/tests/lib/rules/jsx-closing-bracket-location.js index 9f2ff63584..1127af8d89 100644 --- a/tests/lib/rules/jsx-closing-bracket-location.js +++ b/tests/lib/rules/jsx-closing-bracket-location.js @@ -26,10 +26,10 @@ const MESSAGE_PROPS_ALIGNED = 'The closing bracket must be aligned with the last const MESSAGE_TAG_ALIGNED = 'The closing bracket must be aligned with the opening tag'; const MESSAGE_LINE_ALIGNED = 'The closing bracket must be aligned with the line containing the opening tag'; -const messageWithDetails = function (message, expectedColumn, expectedNextLine) { +function messageWithDetails(message, expectedColumn, expectedNextLine) { const details = ` (expected column ${expectedColumn}${expectedNextLine ? ' on the next line)' : ')'}`; return message + details; -}; +} // ------------------------------------------------------------------------------ // Tests From d3b5d717c170b3afb5596effd845f8ca34327978 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Wed, 30 Oct 2019 10:03:58 +0100 Subject: [PATCH 15/36] [New] `jsx-no-target-blank`: add `allowReferrer` option --- docs/rules/jsx-no-target-blank.md | 3 ++- lib/rules/jsx-no-target-blank.js | 14 +++++++++++--- tests/lib/rules/jsx-no-target-blank.js | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/rules/jsx-no-target-blank.md b/docs/rules/jsx-no-target-blank.md index 41211eb1c0..0abc0719ea 100644 --- a/docs/rules/jsx-no-target-blank.md +++ b/docs/rules/jsx-no-target-blank.md @@ -14,10 +14,11 @@ This rule aims to prevent user generated links from creating security vulnerabil ## Rule Options ```json ... -"react/jsx-no-target-blank": [, { "enforceDynamicLinks": }] +"react/jsx-no-target-blank": [, { "allowReferrer": , "enforceDynamicLinks": }] ... ``` +* allow-referrer: optional boolean. If `true` does not require `noreferrer`. Defaults to `false`. * enabled: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. * enforce: optional string, 'always' or 'never' diff --git a/lib/rules/jsx-no-target-blank.js b/lib/rules/jsx-no-target-blank.js index af825f56d2..51df0ece1a 100644 --- a/lib/rules/jsx-no-target-blank.js +++ b/lib/rules/jsx-no-target-blank.js @@ -40,11 +40,11 @@ function hasDynamicLink(element, linkAttribute) { attr.value.type === 'JSXExpressionContainer'); } -function hasSecureRel(element) { +function hasSecureRel(element, allowReferrer) { return element.attributes.find((attr) => { if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') { const tags = attr.value && attr.value.type === 'Literal' && attr.value.value.toLowerCase().split(' '); - return tags && (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0); + return tags && (tags.indexOf('noopener') >= 0 && (allowReferrer || tags.indexOf('noreferrer') >= 0)); } return false; }); @@ -61,6 +61,9 @@ module.exports = { schema: [{ type: 'object', properties: { + allowReferrer: { + type: 'boolean' + }, enforceDynamicLinks: { enum: ['always', 'never'] } @@ -71,12 +74,17 @@ module.exports = { create(context) { const configuration = context.options[0] || {}; + const allowReferrer = configuration.allowReferrer || false; const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'; const components = linkComponentsUtil.getLinkComponents(context); return { JSXAttribute(node) { - if (!components.has(node.parent.name.name) || !isTargetBlank(node) || hasSecureRel(node.parent)) { + if ( + !components.has(node.parent.name.name) || + !isTargetBlank(node) || + hasSecureRel(node.parent, allowReferrer) + ) { return; } diff --git a/tests/lib/rules/jsx-no-target-blank.js b/tests/lib/rules/jsx-no-target-blank.js index b0d97df283..aefd526cfc 100644 --- a/tests/lib/rules/jsx-no-target-blank.js +++ b/tests/lib/rules/jsx-no-target-blank.js @@ -68,6 +68,10 @@ ruleTester.run('jsx-no-target-blank', rule, { code: '', options: [{enforceDynamicLinks: 'never'}], settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}} + }, + { + code: '', + options: [{allowReferrer: true}] } ], invalid: [{ From cfef664b0600d0a363aa341c8f417b3b060918b6 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 28 Nov 2019 22:32:16 -0800 Subject: [PATCH 16/36] [Dev Deps] update `@types/eslint`, `@types/estree`, `@types/node`, `coveralls`, `typescript` --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e88979f187..fc2f5ade93 100644 --- a/package.json +++ b/package.json @@ -38,18 +38,18 @@ "resolve": "^1.12.0" }, "devDependencies": { - "@types/eslint": "^4.16.6", - "@types/estree": "0.0.39", - "@types/node": "^12.0.0", + "@types/eslint": "^6.1.3", + "@types/estree": "0.0.40", + "@types/node": "^12.12.14", "babel-eslint": "^8.2.6", - "coveralls": "^3.0.2", + "coveralls": "^3.0.9", "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "eslint-config-airbnb-base": "^13.2.0", "eslint-plugin-import": "^2.18.2", "istanbul": "^0.4.5", "mocha": "^5.2.0", "sinon": "^7.5.0", - "typescript": "^3.6.3", + "typescript": "^3.7.2", "typescript-eslint-parser": "^20.1.1" }, "peerDependencies": { From 89911ded99253b6804f3e26da6a11ed93fcead42 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 28 Nov 2019 22:33:41 -0800 Subject: [PATCH 17/36] [Deps] update `jsx-ast-utils`, `object.fromentries`, `resolve` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index fc2f5ade93..b1fc6ee2d1 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,12 @@ "doctrine": "^2.1.0", "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1", + "jsx-ast-utils": "^2.2.3", "object.entries": "^1.1.0", - "object.fromentries": "^2.0.0", + "object.fromentries": "^2.0.1", "object.values": "^1.1.0", "prop-types": "^15.7.2", - "resolve": "^1.12.0" + "resolve": "^1.13.1" }, "devDependencies": { "@types/eslint": "^6.1.3", From 027ebd98fe29325c84ea816440ce021b05a8ec96 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 28 Nov 2019 23:20:43 -0800 Subject: [PATCH 18/36] Update CHANGELOG and bump version --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f0bfec7d..42b35c6a3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,59 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). +## [7.17.0] - 2019-11-28 + +### Added + * [`jsx-no-target-blank`][]: add `allowReferrer` option ([#2478][] @eps1lon) + * [`jsx-handler-names`][]: add `checkLocalVariables` option ([#2470][] @aub) + * [`prop-types`][]: Support Flow Type spread ([#2446][] @moroine) + * [`jsx-props-no-spreading`][]: add `explicitSpread` option to allow explicit spread of props ([#2449][] @pawelnvk) + * [`jsx-no-target-blank`][]: warn on `target={'_blank'}` expressions ([#2451][] @timkraut) + +### Fixed + * [`sort-prop-types`][], [`jsx-sort-default-props`][]: disable broken autofix ([#2505][] @webOS101) + * [`no-typos`][]: improve report location ([#2468][] @golopot) + * [`jsx-no-literals`][]: trim whitespace for `allowedStrings` check ([#2436][] @cainlevy) + * [`jsx-curly-brace-presence`][]: Fix filter of undefined error with whitespace inside jsx attr curlies ([#2460][] @dustinyoste) + * [`no-render-return-value`][]: should warn when used in assignment expression ([#2462][] @jichu4n) + * [`jsx-curly-brace-presence`][]: allow trailing spaces in literal ([#2448][] @doochik) + +### Changed + * [Deps] update `jsx-ast-utils`, `object.fromentries`, `resolve` + * [eslint] fix func-names and change object-shorthand to 'always' ([#2483][] @golopot) + * [Docs] `jsx-first-prop-new-line`: Fix documentation formatting ([#2489][] @pjg) + * [Docs] [`prop-types`][]: Update 'skipUndeclared' in rule options ([#2504][] @cjnickel) + * [Docs] [`jsx-first-prop-new-line`][]: fix wrong rule name ([#2500][] @zgayjjf) + * [eslint] enable eslint-plugin-eslint-plugin ([#2469][] @golopot) + * [Docs] [`jsx-props-no-multi-spaces`][]: suggest using core rule instead ([#2463][] @golopot) + * [Docs] [`jsx-first-prop-new-line`][]: add rule options ([#2465][] @SerdarMustafa1) + * [Docs] [`jsx-no-target-blank`][]: Add section about overriding for trusted links ([#2438][] @aschriner) + * [Docs] fix typo ([#2453][] @cainwatson) + * [Docs] [`no-unused-prop-types`][]: clean up prose ([#2273][] @coryhouse) + * [Docs] [`jsx-no-bind`][]: add section about React Hooks ([#2443][] @kdex) + +[#2505]: https://github.com/yannickcr/eslint-plugin-react/pull/2505 +[#2504]: https://github.com/yannickcr/eslint-plugin-react/pull/2504 +[#2500]: https://github.com/yannickcr/eslint-plugin-react/pull/2500 +[#2489]: https://github.com/yannickcr/eslint-plugin-react/pull/2489 +[#2483]: https://github.com/yannickcr/eslint-plugin-react/pull/2483 +[#2478]: https://github.com/yannickcr/eslint-plugin-react/pull/2478 +[#2470]: https://github.com/yannickcr/eslint-plugin-react/pull/2470 +[#2469]: https://github.com/yannickcr/eslint-plugin-react/pull/2469 +[#2468]: https://github.com/yannickcr/eslint-plugin-react/pull/2468 +[#2465]: https://github.com/yannickcr/eslint-plugin-react/pull/2465 +[#2463]: https://github.com/yannickcr/eslint-plugin-react/pull/2463 +[#2460]: https://github.com/yannickcr/eslint-plugin-react/pull/2460 +[#2453]: https://github.com/yannickcr/eslint-plugin-react/pull/2453 +[#2451]: https://github.com/yannickcr/eslint-plugin-react/pull/2451 +[#2449]: https://github.com/yannickcr/eslint-plugin-react/pull/2449 +[#2448]: https://github.com/yannickcr/eslint-plugin-react/pull/2448 +[#2446]: https://github.com/yannickcr/eslint-plugin-react/pull/2446 +[#2443]: https://github.com/yannickcr/eslint-plugin-react/pull/2443 +[#2438]: https://github.com/yannickcr/eslint-plugin-react/pull/2438 +[#2436]: https://github.com/yannickcr/eslint-plugin-react/pull/2436 +[#2273]: https://github.com/yannickcr/eslint-plugin-react/pull/2273 + ## [7.16.0] - 2019-10-04 ### Added diff --git a/package.json b/package.json index b1fc6ee2d1..88099320e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-react", - "version": "7.16.0", + "version": "7.17.0", "author": "Yannick Croissant ", "description": "React specific linting rules for ESLint", "main": "index.js", From 221164ace90accae81cc7e70591bac9950d6433d Mon Sep 17 00:00:00 2001 From: Aleksei Androsov Date: Fri, 29 Nov 2019 18:05:47 +0300 Subject: [PATCH 19/36] [Fix] `jsx-curly-brace-presence`: allow trailing spaces in TemplateLiteral --- lib/rules/jsx-curly-brace-presence.js | 7 ++++++- tests/lib/rules/jsx-curly-brace-presence.js | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 661fe52337..ad273c2d0a 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -174,8 +174,12 @@ module.exports = { return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value); } + function isStringWithTrailingWhiteSpaces(value) { + return /^\s|\s$/.test(value); + } + function isLiteralWithTrailingWhiteSpaces(node) { - return node.type && node.type === 'Literal' && node.value && /^\s|\s$/.test(node.value); + return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value); } // Bail out if there is any character that needs to be escaped in JSX @@ -202,6 +206,7 @@ module.exports = { expressionType === 'TemplateLiteral' && expression.expressions.length === 0 && expression.quasis[0].value.raw.indexOf('\n') === -1 && + !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw) && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw) && ( jsxUtil.isJSX(JSXExpressionNode.parent) || !containsQuoteCharacters(expression.quasis[0].value.cooked) diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 1e137a6c8e..3d10885cdb 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -280,6 +280,14 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{" space before"}', options: ['never'] }, + { + code: '{`space after `}', + options: ['never'] + }, + { + code: '{` space before`}', + options: ['never'] + }, { code: [''].join('/n'), options: ['never'] @@ -342,6 +350,17 @@ ruleTester.run('jsx-curly-brace-presence', rule, { parser: parsers.BABEL_ESLINT, options: [{children: 'never'}] }, + { + code: ` + + { \`space after \` } + foo + { \` space before\` } + + `, + parser: parsers.BABEL_ESLINT, + options: [{children: 'never'}] + }, { code: ` From 2ecdf36eb31bdcdb452515eccdf0a984775c1ff2 Mon Sep 17 00:00:00 2001 From: Sergei Startsev Date: Sat, 30 Nov 2019 02:57:20 +0300 Subject: [PATCH 20/36] [New] Add `jsx-no-script-url` to prevent usage of `javascript:` URLs --- CHANGELOG.md | 1 + README.md | 1 + docs/rules/jsx-no-script-url.md | 57 +++++++++++++++++ index.js | 1 + lib/rules/jsx-no-script-url.js | 91 ++++++++++++++++++++++++++++ tests/lib/rules/jsx-no-script-url.js | 69 +++++++++++++++++++++ 6 files changed, 220 insertions(+) create mode 100644 docs/rules/jsx-no-script-url.md create mode 100644 lib/rules/jsx-no-script-url.js create mode 100644 tests/lib/rules/jsx-no-script-url.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b35c6a3d..99a23280e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2807,3 +2807,4 @@ If you're still not using React 15 you can keep the old behavior by setting the [`static-property-placement`]: docs/rules/static-property-placement.md [`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md [`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md +[`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md diff --git a/README.md b/README.md index 8629db136a..eba722f89f 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ Enable the rules that you would like to use. * [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes * [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX * [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings +* [react/jsx-no-script-url](docs/rules/jsx-no-script-url.md): Prevent usage of `javascript:` URLs * [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'` * [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX * [react/jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md): Disallow unnecessary fragments (fixable) diff --git a/docs/rules/jsx-no-script-url.md b/docs/rules/jsx-no-script-url.md new file mode 100644 index 0000000000..a73e8e72f2 --- /dev/null +++ b/docs/rules/jsx-no-script-url.md @@ -0,0 +1,57 @@ +# Prevent usage of `javascript:` URLs (react/jsx-no-script-url) + +**In React 16.9** any URLs starting with `javascript:` [scheme](https://wiki.whatwg.org/wiki/URL_schemes#javascript:_URLs) log a warning. +React considers the pattern as a dangerous attack surface, see [details](https://reactjs.org/blog/2019/08/08/react-v16.9.0.html#deprecating-javascript-urls). +**In a future major release**, React will throw an error if it encounters a `javascript:` URL. + +## Rule Details + +The following patterns are considered warnings: + +```jsx + + + +``` + +The following patterns are **not** considered warnings: + +```jsx + + +``` + +## Rule Options +```json +{ + "react/jsx-no-script-url": [ + "error", + [ + { + "name": "Link", + "props": ["to"] + }, + { + "name": "Foo", + "props": ["href", "to"] + } + ] + ] +} +``` + +Allows you to indicate a specific list of properties used by a custom component to be checked. + +### name +Component name. + +### props +List of properties that should be validated. + +The following patterns are considered warnings with the options listed above: + +```jsx + + + +``` diff --git a/index.js b/index.js index c9511bb1e3..6db76d0aee 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,7 @@ const allRules = { 'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'), 'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'), 'jsx-no-literals': require('./lib/rules/jsx-no-literals'), + 'jsx-no-script-url': require('./lib/rules/jsx-no-script-url'), 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), 'jsx-no-useless-fragment': require('./lib/rules/jsx-no-useless-fragment'), 'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'), diff --git a/lib/rules/jsx-no-script-url.js b/lib/rules/jsx-no-script-url.js new file mode 100644 index 0000000000..63cf7dd023 --- /dev/null +++ b/lib/rules/jsx-no-script-url.js @@ -0,0 +1,91 @@ +/** + * @fileoverview Prevent usage of `javascript:` URLs + * @author Sergei Startsev + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +// https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30 +/* eslint-disable-next-line max-len, no-control-regex */ +const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; + +function hasJavaScriptProtocol(attr) { + return attr.value.type === 'Literal' && + isJavaScriptProtocol.test(attr.value.value); +} + +function shouldVerifyElement(node, config) { + const name = node.name && node.name.name; + return name === 'a' || config.find(i => i.name === name); +} + +function shouldVerifyProp(node, config) { + const name = node.name && node.name.name; + const parentName = node.parent.name && node.parent.name.name; + + if (parentName === 'a' && name === 'href') { + return true; + } + + const el = config.find(i => i.name === parentName); + if (!el) { + return false; + } + + const props = el.props || []; + return node.name && props.indexOf(name) !== -1; +} + +module.exports = { + meta: { + docs: { + description: 'Forbid `javascript:` URLs', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-no-script-url') + }, + schema: [{ + type: 'array', + uniqueItems: true, + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + props: { + type: 'array', + items: { + type: 'string', + uniqueItems: true + } + } + }, + required: ['name', 'props'], + additionalProperties: false + } + }] + }, + + create(context) { + const config = context.options[0] || []; + return { + JSXAttribute(node) { + const parent = node.parent; + if (shouldVerifyElement(parent, config) && shouldVerifyProp(node, config) && hasJavaScriptProtocol(node)) { + context.report({ + node, + message: 'A future version of React will block javascript: URLs as a security precaution. ' + + 'Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.' + }); + } + } + }; + } +}; diff --git a/tests/lib/rules/jsx-no-script-url.js b/tests/lib/rules/jsx-no-script-url.js new file mode 100644 index 0000000000..bd2355bede --- /dev/null +++ b/tests/lib/rules/jsx-no-script-url.js @@ -0,0 +1,69 @@ +/** + * @fileoverview Prevent usage of `javascript:` URLs + * @author Sergei Startsev + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/jsx-no-script-url'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({parserOptions}); +const message = 'A future version of React will block javascript: URLs as a security precaution. ' + + 'Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.'; +const defaultErrors = [{message}]; + +ruleTester.run('jsx-no-script-url', rule, { + valid: [ + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''} + ], + invalid: [{ + code: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors, + options: [[{name: 'Foo', props: ['to', 'href']}]] + }, { + code: '', + errors: defaultErrors, + options: [[{name: 'Foo', props: ['to', 'href']}]] + }, { + code: ` +
+ + +
+ `, + errors: [{message}, {message}], + options: [[{name: 'Foo', props: ['to', 'href']}, {name: 'Bar', props: ['link']}]] + }] +}); From 1918430aa3220237a57b76dbcf6860e10dd92eb8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 30 Nov 2019 11:19:43 -0800 Subject: [PATCH 21/36] Revert "[Tests] temporarily pin eslint 6 tests to v6.6" This reverts commit 71c7d01efe41cd448518331fd72bd05bba565680. Thanks to v6.7.2 / https://github.com/eslint/eslint/pull/12616 / https://github.com/eslint/eslint/issues/12614 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c3ec49bfae..33f1c0ff16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ env: global: - TEST=true matrix: - - ESLINT=6.6 + - ESLINT=6 - ESLINT=5 - ESLINT=4 after_success: @@ -27,13 +27,13 @@ matrix: fast_finish: true include: - node_js: 'lts/*' - env: PRETEST=true ESLINT=6.6 + env: PRETEST=true exclude: - node_js: '4' env: ESLINT=5 - node_js: '4' - env: ESLINT=6.6 + env: ESLINT=6 - node_js: '6' - env: ESLINT=6.6 + env: ESLINT=6 allow_failures: - node_js: '11' From b00c0af7930039fda689afba12fdb7d3b0e5bb00 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien <567105+MayhemYDG@users.noreply.github.com> Date: Mon, 2 Dec 2019 16:35:06 +0000 Subject: [PATCH 22/36] [meta] Move eslint-plugin-eslint-plugin to devDeps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88099320e0..0a4d68e89a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "dependencies": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", - "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", "jsx-ast-utils": "^2.2.3", "object.entries": "^1.1.0", @@ -45,6 +44,7 @@ "coveralls": "^3.0.9", "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "eslint-config-airbnb-base": "^13.2.0", + "eslint-plugin-eslint-plugin": "^2.1.0", "eslint-plugin-import": "^2.18.2", "istanbul": "^0.4.5", "mocha": "^5.2.0", From d52002afbdaca39c5b07bc4e41c7d022f9d35249 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 7 Dec 2019 23:15:16 -0800 Subject: [PATCH 23/36] [Tests] avoid running tests on pretest job --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 33f1c0ff16..e78efae210 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_script: - 'if [ -n "${ESLINT-}" ]; then npm install --no-save "eslint@${ESLINT}" ; fi' script: - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' - - 'if [ -n "${TEST-}" ]; then npm run unit-test ; fi' + - 'if [ -z "${PRETEST-}" ] && [ -n "${TEST-}" ]; then npm run unit-test ; fi' env: global: - TEST=true From 4fedc5fecb90e5c5ae3c79a6e8dc49f07b9422f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=87=A4=E5=AE=9D?= Date: Thu, 5 Dec 2019 21:01:56 +0800 Subject: [PATCH 24/36] [Fix] `no-typos`: Compilation error when method name is string instead of identifier --- lib/rules/no-typos.js | 6 +++++- tests/lib/rules/no-typos.js | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index 3c943ad068..f8b6d24039 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -144,7 +144,11 @@ module.exports = { function reportErrorIfLifecycleMethodCasingTypo(node) { LIFECYCLE_METHODS.forEach((method) => { - if (method.toLowerCase() === node.key.name.toLowerCase() && method !== node.key.name) { + let nodeKeyName = node.key.name; + if (node.key.type === 'Literal') { + nodeKeyName = node.key.value; + } + if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { context.report({ node, message: 'Typo in component lifecycle method declaration' diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index bb53d72444..8e6b79f91b 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -203,6 +203,14 @@ ruleTester.run('no-typos', rule, { } `, parserOptions + }, { + code: ` + class Hello extends React.Component { + "componentDidMount"() { } + "my-method"() { } + } + `, + parserOptions }, { code: ` class MyClass { From 0f8d79020e67bbc4ba5830c743bcb20238024797 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Thu, 12 Dec 2019 19:28:29 -0300 Subject: [PATCH 25/36] [Fix] `jsx-curly-brace-presence`: Fix error related to tags line break Fixes #1727. --- lib/rules/jsx-curly-brace-presence.js | 8 ++++++-- tests/lib/rules/jsx-curly-brace-presence.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index ad273c2d0a..95a8806bf7 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -298,7 +298,11 @@ module.exports = { return areRuleConditionsSatisfied(parent, config, OPTION_NEVER); } - function shouldCheckForMissingCurly(parent, config) { + function shouldCheckForMissingCurly(node, config) { + if (node.raw.trim() === '') { + return false; + } + const parent = node.parent; if ( parent.children && parent.children.length === 1 && @@ -322,7 +326,7 @@ module.exports = { }, 'Literal, JSXText': (node) => { - if (shouldCheckForMissingCurly(node.parent, userConfig)) { + if (shouldCheckForMissingCurly(node, userConfig)) { reportMissingCurly(node); } } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 3d10885cdb..ec295c9337 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -376,6 +376,21 @@ ruleTester.run('jsx-curly-brace-presence', rule, { Bar}> ` + }, + { + code: ` + +
+

+ + {"foo"} + +

+
+
+ `, + parser: parsers.BABEL_ESLINT, + options: [{children: 'always'}] } ], From fd3cebb6c59660b8185dea7106afb9290674e4d6 Mon Sep 17 00:00:00 2001 From: Dawid van der Hoven Date: Wed, 15 Nov 2017 12:39:18 +0200 Subject: [PATCH 26/36] [Fix] `no-unknown-property`: allowTransparency does not exist in React >= v16.1 --- lib/rules/no-unknown-property.js | 20 +++++++++++++++----- lib/util/version.js | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index b3c643514d..9152f5e9c8 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const versionUtil = require('../util/version'); // ------------------------------------------------------------------------------ // Constants @@ -117,7 +118,7 @@ const SVGDOM_ATTRIBUTE_NAMES = { const DOM_PROPERTY_NAMES = [ // Standard - 'acceptCharset', 'accessKey', 'allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay', + 'acceptCharset', 'accessKey', 'allowFullScreen', 'autoComplete', 'autoFocus', 'autoPlay', 'cellPadding', 'cellSpacing', 'classID', 'className', 'colSpan', 'contentEditable', 'contextMenu', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', 'frameBorder', 'hrefLang', 'htmlFor', 'httpEquiv', 'inputMode', 'keyParams', 'keyType', 'marginHeight', 'marginWidth', @@ -133,6 +134,13 @@ const DOM_PROPERTY_NAMES = [ 'autoSave', 'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemID' ]; +function getDOMPropertyNames(context) { + // this was removed in React v16.1+, see https://github.com/facebook/react/pull/10823 + if (!versionUtil.testReactVersion(context, '16.1.0')) { + return ['allowTransparency'].concat(DOM_PROPERTY_NAMES); + } + return DOM_PROPERTY_NAMES; +} // ------------------------------------------------------------------------------ // Helpers @@ -185,9 +193,10 @@ function tagNameHasDot(node) { /** * Get the standard name of the attribute. * @param {String} name - Name of the attribute. + * @param {String} context - eslint context * @returns {String} The standard name of the attribute. */ -function getStandardName(name) { +function getStandardName(name, context) { if (DOM_ATTRIBUTE_NAMES[name]) { return DOM_ATTRIBUTE_NAMES[name]; } @@ -195,11 +204,12 @@ function getStandardName(name) { return SVGDOM_ATTRIBUTE_NAMES[name]; } let i = -1; - const found = DOM_PROPERTY_NAMES.some((element, index) => { + const names = getDOMPropertyNames(context); + const found = names.some((element, index) => { i = index; return element.toLowerCase() === name; }); - return found ? DOM_PROPERTY_NAMES[i] : null; + return found ? names[i] : null; } // ------------------------------------------------------------------------------ @@ -262,7 +272,7 @@ module.exports = { }); } - const standardName = getStandardName(name); + const standardName = getStandardName(name, context); if (!isTagName(node) || !standardName) { return; } diff --git a/lib/util/version.js b/lib/util/version.js index 7ab9a39ebd..ff7397c07f 100644 --- a/lib/util/version.js +++ b/lib/util/version.js @@ -35,7 +35,7 @@ function detectReactVersion() { function getReactVersionFromContext(context) { let confVer = '999.999.999'; // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings) - if (context.settings.react && context.settings.react.version) { + if (context.settings && context.settings.react && context.settings.react.version) { let settingsVersion = context.settings.react.version; if (settingsVersion === 'detect') { settingsVersion = detectReactVersion(); From 8df49431a66cea059b8f332ce366b07edeb1ef8a Mon Sep 17 00:00:00 2001 From: Hugo Dozois Date: Fri, 25 Nov 2016 09:34:35 -0800 Subject: [PATCH 27/36] [Tests] `no-unused-prop-types`: Added test cases --- tests/lib/rules/no-unused-prop-types.js | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index 0b32121c48..5801ab71d6 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -1094,6 +1094,30 @@ ruleTester.run('no-unused-prop-types', rule, { '}' ].join('\n'), parser: parsers.BABEL_ESLINT + }, { + code: [ + 'type Props = {notTarget: string};', + 'class Hello extends React.Component {', + ' props: Props;', + ' onEvent({ target }: { target: Object }) {};', + ' render () {', + ' return
Hello {this.props.notTarget}
;', + ' }', + '}' + ].join('\n'), + parser: parsers.BABEL_ESLINT + }, { + code: [ + 'type Props = {notTarget: string};', + 'class Hello extends React.Component {', + ' props: Props;', + ' onEvent(infos: { target: Object }) {};', + ' render () {', + ' return
Hello {this.props.notTarget}
;', + ' }', + '}' + ].join('\n'), + parser: parsers.BABEL_ESLINT }, { code: [ 'class Hello extends React.Component {', @@ -5081,6 +5105,21 @@ ruleTester.run('no-unused-prop-types', rule, { errors: [{ message: '\'person.lastname\' PropType is defined but prop is never used' }] + }, { + code: [ + 'type Props = {notTarget: string, unused: string};', + 'class Hello extends React.Component {', + ' props: Props;', + ' onEvent = ({ target }: { target: Object }) => {};', + ' render () {', + ' return
Hello {this.props.notTarget}
;', + ' }', + '}' + ].join('\n'), + parser: parsers.BABEL_ESLINT, + errors: [ + {message: '\'unused\' PropType is defined but prop is never used'} + ] }, { code: ` import PropTypes from 'prop-types'; From f02e8aeee59d2dd08303d0b5776041a8ab860d48 Mon Sep 17 00:00:00 2001 From: toshi-toma Date: Tue, 24 Dec 2019 01:22:49 +0900 Subject: [PATCH 28/36] [Fix] `prop-types`: Does not validate missing propTypes for LogicalExpression --- lib/util/Components.js | 41 +++++++++++++++++++++++++---------- tests/lib/rules/prop-types.js | 28 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/lib/util/Components.js b/lib/util/Components.js index c4334c8c0f..515a39dd00 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -46,6 +46,30 @@ function mergeUsedPropTypes(propsList, newPropsList) { return propsList.concat(propsToAdd); } +function isReturnsConditionalJSX(node, property, strict) { + const returnsConditionalJSXConsequent = node[property] && + node[property].type === 'ConditionalExpression' && + jsxUtil.isJSX(node[property].consequent); + const returnsConditionalJSXAlternate = node[property] && + node[property].type === 'ConditionalExpression' && + jsxUtil.isJSX(node[property].alternate); + return strict ? + (returnsConditionalJSXConsequent && returnsConditionalJSXAlternate) : + (returnsConditionalJSXConsequent || returnsConditionalJSXAlternate); +} + +function isReturnsLogicalJSX(node, property, strict) { + const returnsLogicalJSXLeft = node[property] && + node[property].type === 'LogicalExpression' && + jsxUtil.isJSX(node[property].left); + const returnsLogicalJSXRight = node[property] && + node[property].type === 'LogicalExpression' && + jsxUtil.isJSX(node[property].right); + return strict ? + (returnsLogicalJSXLeft && returnsLogicalJSXRight) : + (returnsLogicalJSXLeft || returnsLogicalJSXRight); +} + const Lists = new WeakMap(); /** @@ -373,22 +397,15 @@ function componentRule(rule, context) { return false; } - const returnsConditionalJSXConsequent = node[property] && - node[property].type === 'ConditionalExpression' && - jsxUtil.isJSX(node[property].consequent); - const returnsConditionalJSXAlternate = node[property] && - node[property].type === 'ConditionalExpression' && - jsxUtil.isJSX(node[property].alternate); - const returnsConditionalJSX = strict ? - (returnsConditionalJSXConsequent && returnsConditionalJSXAlternate) : - (returnsConditionalJSXConsequent || returnsConditionalJSXAlternate); + const returnsConditionalJSX = isReturnsConditionalJSX(node, property, strict); + const returnsLogicalJSX = isReturnsLogicalJSX(node, property, strict); - const returnsJSX = node[property] && - jsxUtil.isJSX(node[property]); + const returnsJSX = node[property] && jsxUtil.isJSX(node[property]); const returnsPragmaCreateElement = this.isCreateElement(node[property]); - return Boolean( + return !!( returnsConditionalJSX || + returnsLogicalJSX || returnsJSX || returnsPragmaCreateElement ); diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index b55fd6e0db..7f1ec41835 100755 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -2465,6 +2465,19 @@ ruleTester.run('prop-types', rule, { pragma: 'Foo' } } + }, + { + code: ` + const Foo = ({length, ordering}) => ( + length > 0 && ( + + ) + ); + Foo.propTypes = { + length: PropTypes.number, + ordering: PropTypes.array + }; + ` } ], @@ -4895,6 +4908,21 @@ ruleTester.run('prop-types', rule, { errors: [{ message: '\'foo.baz\' is missing in props validation' }] + }, + { + code: ` + const Foo = ({length, ordering}) => ( + length > 0 && ( + + ) + ); + `, + errors: [{ + message: '\'length\' is missing in props validation' + }, + { + message: '\'ordering\' is missing in props validation' + }] } ] }); From 1ef658a781698001ac3212a9d0129d9c20a83225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pires=20Barreira?= Date: Thu, 26 Dec 2019 16:14:09 +0000 Subject: [PATCH 29/36] [Docs] `jsx-first-prop-new-line`: Fix rule name in "Rule Options" section --- docs/rules/jsx-first-prop-new-line.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/jsx-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md index e40a4a2dc9..9810eb07e1 100644 --- a/docs/rules/jsx-first-prop-new-line.md +++ b/docs/rules/jsx-first-prop-new-line.md @@ -102,7 +102,7 @@ The following patterns are **not** considered warnings when configured `"multili ## Rule Options ```jsx -"react/jsx-first-props-per-line": `"always" | "never" | "multiline" | "multiline-multiprop"` +"react/jsx-first-prop-new-line": `"always" | "never" | "multiline" | "multiline-multiprop"` ``` ## When not to use From 7f87310ae922cd43653ef913f7d1e1404029ad30 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Thu, 12 Dec 2019 22:51:15 -0300 Subject: [PATCH 30/36] [Fix] `jsx-curly-brace-presence`: Fix `curly-brace-presence` edge cases --- lib/rules/jsx-curly-brace-presence.js | 55 ++++++++++++++++-- tests/lib/rules/jsx-curly-brace-presence.js | 63 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 95a8806bf7..deeda0bc97 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -62,6 +62,7 @@ module.exports = { }, create(context) { + const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g; const ruleOptions = context.options[0]; const userConfig = typeof ruleOptions === 'string' ? {props: ruleOptions, children: ruleOptions} : @@ -76,7 +77,11 @@ module.exports = { } function containsHTMLEntity(rawStringValue) { - return /&[A-Za-z\d#]+;/.test(rawStringValue); + return HTML_ENTITY_REGEX().test(rawStringValue); + } + + function containsOnlyHtmlEntities(rawStringValue) { + return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''; } function containsDisallowedJSXTextChars(rawStringValue) { @@ -111,6 +116,42 @@ module.exports = { return false; } + function isLineBreak(text) { + return containsLineTerminators(text) && text.trim() === ''; + } + + function wrapNonHTMLEntities(text) { + const HTML_ENTITY = ''; + const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map(word => ( + word === '' ? '' : `{${JSON.stringify(word)}}` + )).join(HTML_ENTITY); + + const htmlEntities = text.match(HTML_ENTITY_REGEX()); + return htmlEntities.reduce((acc, htmlEntitiy) => ( + acc.replace(HTML_ENTITY, htmlEntitiy) + ), withCurlyBraces); + } + + function wrapWithCurlyBraces(rawText) { + if (!containsLineTerminators(rawText)) { + return `{${JSON.stringify(rawText)}}`; + } + + return rawText.split('\n').map((line) => { + if (line.trim() === '') { + return line; + } + const firstCharIndex = line.search(/[^\s]/); + const leftWhitespace = line.slice(0, firstCharIndex); + const text = line.slice(firstCharIndex); + + if (containsHTMLEntity(line)) { + return `${leftWhitespace}${wrapNonHTMLEntities(text)}`; + } + return `${leftWhitespace}{${JSON.stringify(text)}}`; + }).join('\n'); + } + /** * Report and fix an unnecessary curly brace violation on a node * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression @@ -153,8 +194,9 @@ module.exports = { // by either using the real character or the unicode equivalent. // If it contains any line terminator character, bail out as well. if ( - containsHTMLEntity(literalNode.raw) || - containsLineTerminators(literalNode.raw) + containsOnlyHtmlEntities(literalNode.raw) || + (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw)) || + isLineBreak(literalNode.raw) ) { return null; } @@ -163,7 +205,7 @@ module.exports = { `{"${escapeDoubleQuotes(escapeBackslashes( literalNode.raw.substring(1, literalNode.raw.length - 1) ))}"}` : - `{${JSON.stringify(literalNode.value)}}`; + wrapWithCurlyBraces(literalNode.raw); return fixer.replaceText(literalNode, expression); } @@ -299,7 +341,10 @@ module.exports = { } function shouldCheckForMissingCurly(node, config) { - if (node.raw.trim() === '') { + if ( + isLineBreak(node.raw) || + containsOnlyHtmlEntities(node.raw) + ) { return false; } const parent = node.parent; diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index ec295c9337..2b7268741d 100755 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -391,6 +391,15 @@ ruleTester.run('jsx-curly-brace-presence', rule, { `, parser: parsers.BABEL_ESLINT, options: [{children: 'always'}] + }, + { + code: ` + +   +   + + `, + options: [{children: 'always'}] } ], @@ -698,6 +707,60 @@ ruleTester.run('jsx-curly-brace-presence', rule, { {message: missingCurlyMessage}, {message: missingCurlyMessage} ], options: ['always'] + }, + { + code: ` + + foo bar +
foo bar foo
+ + foo bar foo bar + + foo bar + + +
+ `, + output: ` + + {"foo bar"} +
{"foo bar foo"}
+ + {"foo bar "}{"foo bar"} + + {"foo bar"} + + +
+ `, + errors: [ + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage}, + {message: missingCurlyMessage} + ], + options: [{children: 'always'}] + }, + { + code: ` + + <Component> +    +   + + `, + output: ` + + <{"Component"}> +    +   + + `, + errors: [ + {message: missingCurlyMessage} + ], + options: [{children: 'always'}] } ] }); From 3824dc912376ad7bf4947d41437102eca1a67627 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 27 Dec 2019 15:38:35 -0800 Subject: [PATCH 31/36] `no-typos`: improve error messages --- lib/rules/no-typos.js | 9 ++- tests/lib/rules/no-typos.js | 112 ++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index f8b6d24039..98dbef855b 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -49,7 +49,8 @@ module.exports = { if (node.name !== 'isRequired') { context.report({ node, - message: `Typo in prop type chain qualifier: ${node.name}` + message: 'Typo in prop type chain qualifier: {{name}}', + data: {name: node.name} }); } } @@ -58,7 +59,8 @@ module.exports = { if (node.name && !PROP_TYPES.some(propTypeName => propTypeName === node.name)) { context.report({ node, - message: `Typo in declared prop type: ${node.name}` + message: 'Typo in declared prop type: {{name}}', + data: {name: node.name} }); } } @@ -151,7 +153,8 @@ module.exports = { if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { context.report({ node, - message: 'Typo in component lifecycle method declaration' + message: 'Typo in component lifecycle method declaration: {{actual}} should be {{expected}}', + data: {actual: nodeKeyName, expected: method} }); } }); diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 8e6b79f91b..ccb20f6de1 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -27,7 +27,7 @@ const parserOptions = { const ERROR_MESSAGE = 'Typo in static class property declaration'; const ERROR_MESSAGE_ES5 = 'Typo in property declaration'; -const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration'; +const ERROR_MESSAGE_LIFECYCLE_METHOD = ({actual, expected}) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`; const ruleTester = new RuleTester(); ruleTester.run('no-typos', rule, { @@ -840,43 +840,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetDerivedStateFromProps', expected: 'getDerivedStateFromProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillMount', expected: 'UNSAFE_componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillReceiveProps', expected: 'UNSAFE_componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillUpdate', expected: 'UNSAFE_componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetSnapshotBeforeUpdate', expected: 'getSnapshotBeforeUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidCatch', expected: 'componentDidCatch'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), type: 'MethodDefinition' }] }, { @@ -902,47 +902,47 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getderivedstatefromprops', expected: 'getDerivedStateFromProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillmount', expected: 'componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillmount', expected: 'UNSAFE_componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidmount', expected: 'componentDidMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillreceiveprops', expected: 'componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Shouldcomponentupdate', expected: 'shouldComponentUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillupdate', expected: 'componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidupdate', expected: 'componentDidUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidcatch', expected: 'componentDidCatch'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillunmount', expected: 'componentWillUnmount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, - type: 'MethodDefinition' + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Render', expected: 'render'}), + nodeType: 'MethodDefinition' }] }, { code: ` @@ -967,43 +967,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getderivedstatefromprops', expected: 'getDerivedStateFromProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillmount', expected: 'componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillmount', expected: 'UNSAFE_componentWillMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidmount', expected: 'componentDidMount'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillreceiveprops', expected: 'componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'shouldcomponentupdate', expected: 'shouldComponentUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillupdate', expected: 'componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidupdate', expected: 'componentDidUpdate'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidcatch', expected: 'componentDidCatch'}), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillunmount', expected: 'componentWillUnmount'}), type: 'MethodDefinition' }] }, { @@ -1583,25 +1583,25 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), type: 'Property' }] }, { @@ -1635,25 +1635,25 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), type: 'Property' }] /* From c1ed90e1cfdd303095815e563d0d15ec359fd830 Mon Sep 17 00:00:00 2001 From: Benjamim Sonntag Date: Fri, 5 Oct 2018 15:01:35 +0100 Subject: [PATCH 32/36] [New] `no-typos`: check static lifecycle methods This updates the `no-typos` rule so that it checks for lifecycle methods that should be static, like `getDerivedStateFromProps`. --- docs/rules/no-typos.md | 3 + lib/rules/no-typos.js | 19 +++-- tests/lib/rules/no-typos.js | 139 ++++++++++++++++++++++-------------- 3 files changed, 102 insertions(+), 59 deletions(-) diff --git a/docs/rules/no-typos.md b/docs/rules/no-typos.md index 87d0a94fd9..1a1c3d92ce 100644 --- a/docs/rules/no-typos.md +++ b/docs/rules/no-typos.md @@ -86,6 +86,9 @@ class MyComponent extends React.Component { } } +class MyComponent extends React.Component { + getDerivedStateFromProps() {} +} ``` The following patterns are **not** considered warnings: diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index 98dbef855b..ec1d6a25d6 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -13,6 +13,7 @@ const docsUrl = require('../util/docsUrl'); // ------------------------------------------------------------------------------ const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps']; +const STATIC_LIFECYCLE_METHODS = ['getDerivedStateFromProps']; const LIFECYCLE_METHODS = [ 'getDerivedStateFromProps', 'componentWillMount', @@ -145,11 +146,21 @@ module.exports = { } function reportErrorIfLifecycleMethodCasingTypo(node) { - LIFECYCLE_METHODS.forEach((method) => { - let nodeKeyName = node.key.name; - if (node.key.type === 'Literal') { - nodeKeyName = node.key.value; + let nodeKeyName = node.key.name; + if (node.key.type === 'Literal') { + nodeKeyName = node.key.value; + } + + STATIC_LIFECYCLE_METHODS.forEach((method) => { + if (!node.static && nodeKeyName.toLowerCase() === method.toLowerCase()) { + context.report({ + node, + message: `Lifecycle method should be static: ${nodeKeyName}` + }); } + }); + + LIFECYCLE_METHODS.forEach((method) => { if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { context.report({ node, diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index ccb20f6de1..9a7b6f751c 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -27,7 +27,8 @@ const parserOptions = { const ERROR_MESSAGE = 'Typo in static class property declaration'; const ERROR_MESSAGE_ES5 = 'Typo in property declaration'; -const ERROR_MESSAGE_LIFECYCLE_METHOD = ({actual, expected}) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`; +const ERROR_MESSAGE_LIFECYCLE_METHOD = (actual, expected) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`; +const ERROR_MESSAGE_STATIC = method => `Lifecycle method should be static: ${method}`; const ruleTester = new RuleTester(); ruleTester.run('no-typos', rule, { @@ -190,6 +191,7 @@ ruleTester.run('no-typos', rule, { }, { code: ` class Hello extends React.Component { + static getDerivedStateFromProps() { } componentWillMount() { } componentDidMount() { } componentWillReceiveProps() { } @@ -840,43 +842,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetDerivedStateFromProps', expected: 'getDerivedStateFromProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillMount', expected: 'UNSAFE_componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillMount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillReceiveProps', expected: 'UNSAFE_componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillReceiveProps', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillUpdate', expected: 'UNSAFE_componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillUpdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetSnapshotBeforeUpdate', expected: 'getSnapshotBeforeUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetSnapshotBeforeUpdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidCatch', expected: 'componentDidCatch'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidCatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -902,46 +904,46 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getderivedstatefromprops', expected: 'getDerivedStateFromProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillmount', expected: 'componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillmount', expected: 'UNSAFE_componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidmount', expected: 'componentDidMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillreceiveprops', expected: 'componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Shouldcomponentupdate', expected: 'shouldComponentUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillupdate', expected: 'componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidupdate', expected: 'componentDidUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidcatch', expected: 'componentDidCatch'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillunmount', expected: 'componentWillUnmount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Render', expected: 'render'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Render', 'render'), nodeType: 'MethodDefinition' }] }, { @@ -967,43 +969,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getderivedstatefromprops', expected: 'getDerivedStateFromProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillmount', expected: 'componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillmount', expected: 'UNSAFE_componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidmount', expected: 'componentDidMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillreceiveprops', expected: 'componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'shouldcomponentupdate', expected: 'shouldComponentUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillupdate', expected: 'componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidupdate', expected: 'componentDidUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidcatch', expected: 'componentDidCatch'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillunmount', expected: 'componentWillUnmount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -1583,27 +1585,54 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'Property' }] + }, { + code: ` + class Hello extends React.Component { + getDerivedStateFromProps() { } + } + `, + parser: parsers.BABEL_ESLINT, + parserOptions, + errors: [{ + message: ERROR_MESSAGE_STATIC('getDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }] + }, { + code: ` + class Hello extends React.Component { + GetDerivedStateFromProps() { } + } + `, + parser: parsers.BABEL_ESLINT, + parserOptions, + errors: [{ + message: ERROR_MESSAGE_STATIC('GetDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }] }, { code: ` import React from 'react'; @@ -1635,25 +1664,25 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}), + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'Property' }] /* From 0d7019b48f1d42bb34065c5a321ec66acbc264ca Mon Sep 17 00:00:00 2001 From: Sean Hayes Date: Wed, 19 Apr 2017 13:13:13 -0700 Subject: [PATCH 33/36] [New] `no-adjacent-inline-elements`: Prevent adjacent inline elements not separated by whitespace --- CHANGELOG.md | 1 + docs/rules/no-adjacent-inline-elements.md | 24 ++++ index.js | 1 + lib/rules/no-adjacent-inline-elements.js | 115 ++++++++++++++++++ .../lib/rules/no-adjacent-inline-elements.js | 102 ++++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 docs/rules/no-adjacent-inline-elements.md create mode 100644 lib/rules/no-adjacent-inline-elements.js create mode 100644 tests/lib/rules/no-adjacent-inline-elements.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a23280e5..b67ee92808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2808,3 +2808,4 @@ If you're still not using React 15 you can keep the old behavior by setting the [`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md [`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md [`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md +[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md diff --git a/docs/rules/no-adjacent-inline-elements.md b/docs/rules/no-adjacent-inline-elements.md new file mode 100644 index 0000000000..db7d9b41ec --- /dev/null +++ b/docs/rules/no-adjacent-inline-elements.md @@ -0,0 +1,24 @@ +# Prevent adjacent inline elements not separated by whitespace. (no-adjacent-inline-elements) + +Adjacent inline elements not separated by whitespace will bump up against each +other when viewed in an unstyled manner, which usually isn't desirable. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +
+
+ +React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]); +``` + +The following patterns are not considered warnings: + +```jsx +
+
+ +React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]); +``` diff --git a/index.js b/index.js index 6db76d0aee..2396353682 100644 --- a/index.js +++ b/index.js @@ -52,6 +52,7 @@ const allRules = { 'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'), 'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'), 'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'), + 'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'), 'no-array-index-key': require('./lib/rules/no-array-index-key'), 'no-children-prop': require('./lib/rules/no-children-prop'), 'no-danger': require('./lib/rules/no-danger'), diff --git a/lib/rules/no-adjacent-inline-elements.js b/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000..60104542b4 --- /dev/null +++ b/lib/rules/no-adjacent-inline-elements.js @@ -0,0 +1,115 @@ +/** + * @fileoverview Prevent adjacent inline elements not separated by whitespace. + * @author Sean Hayes + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements +const inlineNames = [ + 'a', + 'b', + 'big', + 'i', + 'small', + 'tt', + 'abbr', + 'acronym', + 'cite', + 'code', + 'dfn', + 'em', + 'kbd', + 'strong', + 'samp', + 'time', + 'var', + 'bdo', + 'br', + 'img', + 'map', + 'object', + 'q', + 'script', + 'span', + 'sub', + 'sup', + 'button', + 'input', + 'label', + 'select', + 'textarea' +]; +// Note: raw   will be transformed into \u00a0. +const whitespaceRegex = /(?:^\s|\s$)/; + +function isInline(node) { + if (node.type === 'Literal') { + // Regular whitespace will be removed. + const value = node.value; + // To properly separate inline elements, each end of the literal will need + // whitespace. + return !whitespaceRegex.test(value); + } + if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) { + return true; + } + if (node.type === 'CallExpression' && inlineNames.indexOf(node.arguments[0].value) > -1) { + return true; + } + return false; +} + +const ERROR = 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + ERROR, + meta: { + docs: { + description: 'Prevent adjacent inline elements not separated by whitespace.', + category: 'Best Practices', + recommended: false + }, + schema: [] + }, + create(context) { + function validate(node, children) { + let currentIsInline = false; + let previousIsInline = false; + for (let i = 0; i < children.length; i++) { + currentIsInline = isInline(children[i]); + if (previousIsInline && currentIsInline) { + context.report({ + node, + message: ERROR + }); + return; + } + previousIsInline = currentIsInline; + } + } + return { + JSXElement(node) { + validate(node, node.children); + }, + CallExpression(node) { + if (!node.callee || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'createElement') { + return; + } + if (node.arguments.length < 2) { + return; + } + const children = node.arguments[2].elements; + validate(node, children); + } + }; + } +}; diff --git a/tests/lib/rules/no-adjacent-inline-elements.js b/tests/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000..c196a0ee20 --- /dev/null +++ b/tests/lib/rules/no-adjacent-inline-elements.js @@ -0,0 +1,102 @@ +/** + * @fileoverview Tests for no-adjacent-inline-elements + * @author Sean Hayes + */ + +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/no-adjacent-inline-elements'); + +const ERROR = rule.ERROR; + +const parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true + } +}; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester(); +ruleTester.run('no-adjacent-inline-elements', rule, { + valid: [ + { + code: '
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '

;', + parserOptions + }, + { + code: '

;', + parserOptions + }, + { + code: '
 
;', + parserOptions + }, + { + code: '
 some text  
;', + parserOptions + }, + { + code: '
 some text
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '
some text
;', + errors: [{message: ERROR}], + parserOptions + }, + { + code: ('React.createElement("div", undefined, [React.createElement("a"), ' + + '" some text ", React.createElement("a")]);'), + errors: [{message: ERROR}], + parserOptions + }, + { + code: 'React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]);', + errors: [{message: ERROR}], + parserOptions + } + ], + invalid: [ + { + code: '
;', + errors: [{message: ERROR}], + parserOptions + }, + { + code: '
;', + errors: [{message: ERROR}], + parserOptions + }, + { + code: 'React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);', + errors: [{message: ERROR}], + parserOptions + } + ] +}); From 4f3cd90b0713178a4ec627f56722ca4aba675060 Mon Sep 17 00:00:00 2001 From: RedTn Date: Mon, 23 Dec 2019 18:33:22 -0800 Subject: [PATCH 34/36] [New] `require-default-props`: add option to ignore functional components --- CHANGELOG.md | 2 + docs/rules/require-default-props.md | 64 ++++- lib/rules/require-default-props.js | 15 +- tests/lib/rules/require-default-props.js | 324 ++++++++++++++++++++++- 4 files changed, 400 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67ee92808..f77f1748af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel * [`prop-types`][]: Support Flow Type spread ([#2446][] @moroine) * [`jsx-props-no-spreading`][]: add `explicitSpread` option to allow explicit spread of props ([#2449][] @pawelnvk) * [`jsx-no-target-blank`][]: warn on `target={'_blank'}` expressions ([#2451][] @timkraut) + * [`require-default-props`]: add option to ignore functional components ([#2532][] @RedTn) ### Fixed * [`sort-prop-types`][], [`jsx-sort-default-props`][]: disable broken autofix ([#2505][] @webOS101) @@ -34,6 +35,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel * [Docs] [`no-unused-prop-types`][]: clean up prose ([#2273][] @coryhouse) * [Docs] [`jsx-no-bind`][]: add section about React Hooks ([#2443][] @kdex) +[#2532]: https://github.com/yannickcr/eslint-plugin-react/pull/2532 [#2505]: https://github.com/yannickcr/eslint-plugin-react/pull/2505 [#2504]: https://github.com/yannickcr/eslint-plugin-react/pull/2504 [#2500]: https://github.com/yannickcr/eslint-plugin-react/pull/2500 diff --git a/docs/rules/require-default-props.md b/docs/rules/require-default-props.md index 0d7923ad5d..b4796e7ecd 100644 --- a/docs/rules/require-default-props.md +++ b/docs/rules/require-default-props.md @@ -188,12 +188,13 @@ NotAComponent.propTypes = { ```js ... -"react/require-default-props": [, { forbidDefaultForRequired: }] +"react/require-default-props": [, { forbidDefaultForRequired: , ignoreFunctionalComponents: }] ... ``` * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. * `forbidDefaultForRequired`: optional boolean to forbid prop default for a required prop. Defaults to false. +* `ignoreFunctionalComponents`: optional boolean to ignore this rule for functional components. Defaults to false. ### `forbidDefaultForRequired` @@ -269,6 +270,67 @@ MyStatelessComponent.propTypes = { }; ``` +### `ignoreFunctionalComponents` + +When set to `true`, ignores this rule for all functional components. + +The following patterns are warnings: + +```jsx +class Greeting extends React.Component { + render() { + return ( +

Hello, {this.props.foo} {this.props.bar}

+ ); + } + + static propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + + static defaultProps = { + foo: "foo", + bar: "bar" + }; +} +``` + +The following patterns are **not** warnings: + +```jsx +function MyStatelessComponent({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string +}; +``` + +```jsx +const MyStatelessComponent = ({ foo, bar }) => { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string +}; +``` + +```jsx +const MyStatelessComponent = function({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string +}; +``` + ## When Not To Use It If you don't care about using `defaultsProps` for your component's props that are not required, you can disable this rule. diff --git a/lib/rules/require-default-props.js b/lib/rules/require-default-props.js index 1a1544c14f..e744f057b6 100644 --- a/lib/rules/require-default-props.js +++ b/lib/rules/require-default-props.js @@ -7,7 +7,7 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); - +const astUtil = require('../util/ast'); // ------------------------------------------------------------------------------ // Rule Definition @@ -26,6 +26,9 @@ module.exports = { properties: { forbidDefaultForRequired: { type: 'boolean' + }, + ignoreFunctionalComponents: { + type: 'boolean' } }, additionalProperties: false @@ -35,7 +38,7 @@ module.exports = { create: Components.detect((context, components) => { const configuration = context.options[0] || {}; const forbidDefaultForRequired = configuration.forbidDefaultForRequired || false; - + const ignoreFunctionalComponents = configuration.ignoreFunctionalComponents || false; /** * Reports all propTypes passed in that don't have a defaultProps counterpart. @@ -83,7 +86,13 @@ module.exports = { 'Program:exit'() { const list = components.list(); - Object.keys(list).filter(component => list[component].declaredPropTypes).forEach((component) => { + Object.keys(list).filter((component) => { + if (ignoreFunctionalComponents && + (astUtil.isFunction(list[component].node) || astUtil.isFunctionLikeExpression(list[component].node))) { + return false; + } + return list[component].declaredPropTypes; + }).forEach((component) => { reportPropTypesWithoutDefault( list[component].declaredPropTypes, list[component].defaultProps || {} diff --git a/tests/lib/rules/require-default-props.js b/tests/lib/rules/require-default-props.js index c691961ed7..409f4a6250 100644 --- a/tests/lib/rules/require-default-props.js +++ b/tests/lib/rules/require-default-props.js @@ -32,7 +32,7 @@ ruleTester.run('require-default-props', rule, { valid: [ // - // stateless components + // stateless components as function declarations { code: [ 'function MyStatelessComponent({ foo, bar }) {', @@ -152,6 +152,207 @@ ruleTester.run('require-default-props', rule, { 'MyStatelessComponent.defaultProps = defaults;' ].join('\n') }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}] + }, + { + code: [ + 'function MyStatelessComponent({ foo = "test", bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}] + }, + { + code: [ + 'function MyStatelessComponent({ foo = "test", bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + '};' + ].join('\n'), + options: [{forbidDefaultForRequired: true, ignoreFunctionalComponents: true}] + }, + { + code: [ + 'export function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + parser: parsers.BABEL_ESLINT + }, + { + code: [ + 'export function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + parser: parsers.TYPESCRIPT_ESLINT + }, + { + code: [ + 'export default function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + parser: parsers.BABEL_ESLINT + }, + { + code: [ + 'export default function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + parser: parsers.TYPESCRIPT_ESLINT + }, + + // + // stateless components as function expressions + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + const MyComponent = function({ foo, bar }) { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + + export default MyComponent; + `, + options: [{ignoreFunctionalComponents: true}] + }, + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + export const MyComponent = function({ foo, bar }) { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + `, + options: [{ignoreFunctionalComponents: true}], + parser: parsers.BABEL_ESLINT + }, + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + export const MyComponent = function({ foo, bar }) { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + `, + options: [{ignoreFunctionalComponents: true}], + parser: parsers.TYPESCRIPT_ESLINT + }, + + // + // stateless components as arrow function expressions + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + const MyComponent = ({ foo, bar }) => { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + + export default MyComponent; + `, + options: [{ignoreFunctionalComponents: true}] + }, + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + export const MyComponent = ({ foo, bar }) => { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + + export default MyComponent; + `, + options: [{ignoreFunctionalComponents: true}], + parser: parsers.BABEL_ESLINT + }, + { + code: ` + import PropTypes from 'prop-types'; + import React from 'react'; + + export const MyComponent = ({ foo, bar }) => { + return
{foo}{bar}
; + }; + + MyComponent.propTypes = { + foo: PropTypes.string, + bar: PropTypes.string.isRequired + }; + + export default MyComponent; + `, + options: [{ignoreFunctionalComponents: true}], + parser: parsers.TYPESCRIPT_ESLINT + }, // // createReactClass components @@ -214,6 +415,26 @@ ruleTester.run('require-default-props', rule, { '});' ].join('\n') }, + { + code: [ + 'var Greeting = createReactClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' foo: "foo",', + ' bar: "bar"', + ' };', + ' }', + '});' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}] + }, // // ES6 class component @@ -314,6 +535,25 @@ ruleTester.run('require-default-props', rule, { 'Greeting.defaultProps.foo = "foo";' ].join('\n') }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}] + }, // // edge cases @@ -1016,6 +1256,25 @@ ruleTester.run('require-default-props', rule, { column: 5 }] }, + { + code: [ + 'var Greeting = createReactClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + ' }', + '});' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + errors: [{ + message: 'propType "foo" is not required, but has no corresponding defaultProps declaration.', + line: 6, + column: 5 + }] + }, { code: [ 'var Greeting = createReactClass({', @@ -1062,6 +1321,27 @@ ruleTester.run('require-default-props', rule, { column: 3 }] }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + '};' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + errors: [{ + message: 'propType "foo" is not required, but has no corresponding defaultProps declaration.', + line: 9, + column: 3 + }] + }, { code: [ 'class Greeting extends React.Component {', @@ -1215,6 +1495,26 @@ ruleTester.run('require-default-props', rule, { column: 7 }] }, + { + code: [ + 'class Hello extends React.Component {', + ' static get propTypes() {', + ' return {', + ' name: PropTypes.string', + ' };', + ' }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + options: [{ignoreFunctionalComponents: true}], + errors: [{ + message: 'propType "name" is not required, but has no corresponding defaultProps declaration.', + line: 4, + column: 7 + }] + }, { code: [ 'class Hello extends React.Component {', @@ -1312,6 +1612,28 @@ ruleTester.run('require-default-props', rule, { column: 5 }] }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + ' static propTypes = {', + ' foo: PropTypes.string,', + ' bar: PropTypes.string.isRequired', + ' };', + '}' + ].join('\n'), + parser: parsers.BABEL_ESLINT, + options: [{ignoreFunctionalComponents: true}], + errors: [{ + message: 'propType "foo" is not required, but has no corresponding defaultProps declaration.', + line: 8, + column: 5 + }] + }, { code: [ 'class Greeting extends React.Component {', From d02bb9aaa800b0a794c0caf8aca630d54979baba Mon Sep 17 00:00:00 2001 From: golopot Date: Sat, 25 May 2019 14:41:31 +0800 Subject: [PATCH 35/36] [Refactor]: remove unused codes in util/propTypes - remove useless handling of `instanceOf` and `oneOf` There is no special treatment of `Proptypes.{instanceOf, oneOf}` types so they might as well be handled as unrecognized types. --- lib/types.d.ts | 3 +-- lib/util/propTypes.js | 41 ++++------------------------------------- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/lib/types.d.ts b/lib/types.d.ts index 91e5b4bd48..7e22f2788b 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -23,9 +23,8 @@ declare global { [k in string]: TypeDeclarationBuilder; }; - type UnionTypeDefinitionChildren = unknown[]; type UnionTypeDefinition = { type: 'union' | 'shape'; - children: UnionTypeDefinitionChildren | true; + children: unknown[]; }; } diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index dfb2a7efd0..9c2b956829 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -170,22 +170,9 @@ module.exports = function propTypesInstructions(context, components, utils) { /** @type {UnionTypeDefinition} */ const unionTypeDefinition = { type: 'union', - children: [] + children: annotation.types.map(type => buildTypeAnnotationDeclarationTypes(type, parentName, seen)) }; - for (let i = 0, j = annotation.types.length; i < j; i++) { - const type = buildTypeAnnotationDeclarationTypes(annotation.types[i], parentName, seen); - // keep only complex type - if (type.type) { - if (type.children === true) { - // every child is accepted for one type, abort type analysis - unionTypeDefinition.children = true; - return unionTypeDefinition; - } - } - - /** @type {UnionTypeDefinitionChildren} */(unionTypeDefinition.children).push(type); - } - if (/** @type {UnionTypeDefinitionChildren} */(unionTypeDefinition.children).length === 0) { + if (unionTypeDefinition.children.length === 0) { // no complex type found, simply accept everything return {}; } @@ -419,34 +406,14 @@ module.exports = function propTypesInstructions(context, components, utils) { /** @type {UnionTypeDefinition} */ const unionTypeDefinition = { type: 'union', - children: [] + children: argument.elements.map(element => buildReactDeclarationTypes(element, parentName)) }; - for (let i = 0, j = argument.elements.length; i < j; i++) { - const type = buildReactDeclarationTypes(argument.elements[i], parentName); - // keep only complex type - if (type.type) { - if (type.children === true) { - // every child is accepted for one type, abort type analysis - unionTypeDefinition.children = true; - return unionTypeDefinition; - } - } - - /** @type {UnionTypeDefinitionChildren} */(unionTypeDefinition.children).push(type); - } - if (/** @type {UnionTypeDefinitionChildren} */(unionTypeDefinition.children).length === 0) { + if (unionTypeDefinition.children.length === 0) { // no complex type found, simply accept everything return {}; } return unionTypeDefinition; } - case 'instanceOf': - return { - type: 'instance', - // Accept all children because we can't know what type they are - children: true - }; - case 'oneOf': default: return {}; } From 5fac02c27d9b9d10a786c788f7b322009de04426 Mon Sep 17 00:00:00 2001 From: mehmet Date: Mon, 31 Jul 2017 15:04:35 +0300 Subject: [PATCH 36/36] [Fix] `jsx-pascal-case`: false negative with namespacing Fixes #1336. --- lib/rules/jsx-pascal-case.js | 2 +- tests/lib/rules/jsx-pascal-case.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js index fb8ea45427..43472c4067 100644 --- a/lib/rules/jsx-pascal-case.js +++ b/lib/rules/jsx-pascal-case.js @@ -13,7 +13,7 @@ const jsxUtil = require('../util/jsx'); // Constants // ------------------------------------------------------------------------------ -const PASCAL_CASE_REGEX = /^([A-Z0-9]|[A-Z0-9]+[a-z0-9]+(?:[A-Z0-9]+[a-z0-9]*)*)$/; +const PASCAL_CASE_REGEX = /^(.*[.])*([A-Z]|[A-Z]+[a-z0-9]+(?:[A-Z0-9]+[a-z0-9]*)*)$/; const ALL_CAPS_TAG_REGEX = /^[A-Z0-9]+([A-Z0-9_]*[A-Z0-9]+)?$/; // ------------------------------------------------------------------------------ diff --git a/tests/lib/rules/jsx-pascal-case.js b/tests/lib/rules/jsx-pascal-case.js index f15c9b633d..68f0806780 100644 --- a/tests/lib/rules/jsx-pascal-case.js +++ b/tests/lib/rules/jsx-pascal-case.js @@ -45,7 +45,7 @@ ruleTester.run('jsx-pascal-case', rule, { }, { code: '' }, { - code: '' + code: '' }, { code: '' }, { @@ -59,6 +59,8 @@ ruleTester.run('jsx-pascal-case', rule, { options: [{allowAllCaps: true}] }, { code: '' + }, { + code: '' }, { code: '' }, {