diff --git a/.eslintrc b/.eslintrc index a221189379..4a1be50416 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 @@ -15,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/.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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bbcad77f8..313c6a430f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,61 @@ 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) + * [`require-default-props`]: add option to ignore functional components ([#2532][] @RedTn) + +### 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) + +[#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 +[#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 @@ -2754,4 +2809,6 @@ 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 +[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md [`function-component-definition`]: docs/rules/function-component-definition.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-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md index fc6481642a..9810eb07e1 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-first-prop-new-line": `"always" | "never" | "multiline" | "multiline-multiprop"` +``` + ## When not to use If you are not using JSX then you can disable this rule. 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/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/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/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/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. 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/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/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/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" 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/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/index.js b/index.js index de64c9a8cc..53b50230f4 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,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'), @@ -52,6 +53,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/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-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/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-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 76dc70fb4e..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); } @@ -174,8 +216,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 +248,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) @@ -242,12 +289,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); @@ -287,7 +340,14 @@ module.exports = { return areRuleConditionsSatisfied(parent, config, OPTION_NEVER); } - function shouldCheckForMissingCurly(parent, config) { + function shouldCheckForMissingCurly(node, config) { + if ( + isLineBreak(node.raw) || + containsOnlyHtmlEntities(node.raw) + ) { + return false; + } + const parent = node.parent; if ( parent.children && parent.children.length === 1 && @@ -311,7 +371,7 @@ module.exports = { }, 'Literal, JSXText': (node) => { - if (shouldCheckForMissingCurly(node.parent, userConfig)) { + if (shouldCheckForMissingCurly(node, userConfig)) { reportMissingCurly(node); } } 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-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/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-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/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/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/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/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/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-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/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-typos.js b/lib/rules/no-typos.js index 96be82b943..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', @@ -49,7 +50,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 +60,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} }); } } @@ -124,9 +127,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 +138,7 @@ module.exports = { 'Typo in static class property declaration' : 'Typo in property declaration'; context.report({ - node, + node: propertyKey, message }); } @@ -142,11 +146,26 @@ module.exports = { } function reportErrorIfLifecycleMethodCasingTypo(node) { + 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() === node.key.name.toLowerCase() && method !== node.key.name) { + 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} }); } }); @@ -176,9 +195,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 +215,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 +235,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/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-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/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 c652e5bc12..a97483371c 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) { @@ -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..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. @@ -80,10 +83,16 @@ module.exports = { // -------------------------------------------------------------------------- return { - 'Program:exit': function () { + '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/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/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/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/Components.js b/lib/util/Components.js index dd3c88ffd7..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 ); @@ -875,7 +892,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/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..9c2b956829 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; }, @@ -153,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 {}; } @@ -241,7 +245,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 +257,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 +267,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; @@ -393,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 {}; } @@ -732,11 +725,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/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(); diff --git a/package.json b/package.json index 64c0ffd72d..0a4d68e89a 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", @@ -29,26 +29,27 @@ "array-includes": "^3.0.3", "doctrine": "^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": "^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-eslint-plugin": "^2.1.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": { diff --git a/tests/lib/rules/default-props-match-prop-types.js b/tests/lib/rules/default-props-match-prop-types.js index 0e6c6d0be6..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 }, // @@ -733,6 +735,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: [ @@ -793,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 }) {', @@ -1296,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 {', @@ -1460,7 +1515,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 +1644,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/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-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 diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 380d06f787..2b7268741d 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'}] @@ -277,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'] @@ -339,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: ` @@ -354,6 +376,30 @@ ruleTester.run('jsx-curly-brace-presence', rule, { Bar}> ` + }, + { + code: ` + +
+

+ + {"foo"} + +

+
+
+ `, + parser: parsers.BABEL_ESLINT, + options: [{children: 'always'}] + }, + { + code: ` + +   +   + + `, + options: [{children: 'always'}] } ], @@ -505,7 +551,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 ', @@ -660,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'}] } ] }); 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: '' + }, { + 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\''}] 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 ']}] } ], 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']}]] + }] +}); 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: [{ diff --git a/tests/lib/rules/jsx-no-undef.js b/tests/lib/rules/jsx-no-undef.js index 36668edd99..717622ed05 100644 --- a/tests/lib/rules/jsx-no-undef.js +++ b/tests/lib/rules/jsx-no-undef.js @@ -35,7 +35,8 @@ ruleTester.run('jsx-no-undef', rule, { valid: [{ code: '/*eslint no-undef:1*/ var React, App; React.render();' }, { - 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..68f0806780 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', @@ -43,9 +45,12 @@ ruleTester.run('jsx-pascal-case', rule, { }, { code: '' }, { - code: '' + code: '' }, { code: '' + }, { + code: '', + parser: parsers.BABEL_ESLINT }, { code: '', options: [{allowAllCaps: true}] @@ -54,13 +59,13 @@ ruleTester.run('jsx-pascal-case', rule, { options: [{allowAllCaps: true}] }, { code: '' + }, { + code: '' }, { code: '' }, { code: '', options: [{ignore: ['IGNORED']}] - }, { - code: '' }, { code: '<$ />' }, { 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/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-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 + } + ] +}); 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..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 = 'Typo in component lifecycle method declaration'; +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() { } @@ -203,6 +205,14 @@ ruleTester.run('no-typos', rule, { } `, parserOptions + }, { + code: ` + class Hello extends React.Component { + "componentDidMount"() { } + "my-method"() { } + } + `, + parserOptions }, { code: ` class MyClass { @@ -333,19 +343,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 +403,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"; @@ -646,21 +628,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 { @@ -692,7 +674,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 {} @@ -784,21 +766,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 { @@ -860,43 +842,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillMount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillReceiveProps', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillUpdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetSnapshotBeforeUpdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidCatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -922,47 +904,47 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, - type: 'MethodDefinition' + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Render', 'render'), + nodeType: 'MethodDefinition' }] }, { code: ` @@ -987,43 +969,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -1366,6 +1348,7 @@ ruleTester.run('no-typos', rule, { }).isrequired } `, + parser: parsers.BABEL_ESLINT, parserOptions, errors: [{ message: 'Typo in prop type chain qualifier: isrequired' @@ -1594,35 +1577,62 @@ 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, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + 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'; @@ -1646,33 +1656,33 @@ 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, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'Property' }] /* diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index b5ba74ea25..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 {', @@ -4619,6 +4643,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 = { @@ -5002,40 +5049,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 {', @@ -5092,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'; diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index dedf5ff727..7f1ec41835 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 {', @@ -1632,6 +1633,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 +2353,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: ` @@ -2423,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 + }; + ` } ], @@ -4728,6 +4783,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: ` @@ -4829,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' + }] } ] }); 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..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 @@ -514,7 +754,8 @@ ruleTester.run('require-default-props', rule, { ' ...defaults,', ' bar: "bar"', '};' - ].join('\n') + ].join('\n'), + parser: parsers.BABEL_ESLINT }, // @@ -1015,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({', @@ -1061,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 {', @@ -1214,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 {', @@ -1311,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 {', @@ -1789,23 +2112,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 = {', 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) + // }; + // ` }] });