diff --git a/README.md b/README.md index 926a965abd..329e51b3a5 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Enable the rules that you would like to use. * [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order (fixable) * [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting * [react/state-in-constructor](docs/rules/state-in-constructor.md): Enforce the state initialization style to be either in a constructor or with a class property +* [react/static-property-placement](docs/rules/static-property-placement.md): Defines where React component static properties should be positioned. * [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object * [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md): Prevent void DOM elements (e.g. ``, `
`) from receiving children diff --git a/docs/rules/static-property-placement.md b/docs/rules/static-property-placement.md new file mode 100644 index 0000000000..14aa925f72 --- /dev/null +++ b/docs/rules/static-property-placement.md @@ -0,0 +1,185 @@ +# Enforces where React component static properties should be positioned. (static-property-placement) + +This rule allows you to enforce where `childContextTypes`, `contextTypes`, `contextType`, `defaultProps`, `displayName`, +and `propTypes` are declared in an ES6 class. + + +## Rule Details + +By default, this rule will check for and warn about declaring any of the above properties outside of the class body. + +There are three key options are `static public field`, `static getter`, and `property assignment`. + +### When `static public field` is enabled (default): + +Examples of **incorrect** code for this rule: + +```js +class MyComponent extends React.Component { + static get childContextTypes() { /*...*/ } + static get contextTypes() { /*...*/ } + static get contextType() { /*...*/ } + static get displayName() { /*...*/ } + static get defaultProps() { /*...*/ } + static get propTypes() { /*...*/ } +} +``` + +```js +class MyComponent extends React.Component { /*...*/ } +MyComponent.childContextTypes = { /*...*/ }; +MyComponent.contextTypes = { /*...*/ }; +MyComponent.contextType = { /*...*/ }; +MyComponent.displayName = "Hello"; +MyComponent.defaultProps = { /*...*/ }; +MyComponent.propTypes = { /*...*/ }; +``` + +Examples of **correct** code for this rule: + +```js +class MyComponent extends React.Component { + static childContextTypes = { /*...*/ }; + static contextTypes = { /*...*/ }; + static contextType = { /*...*/ }; + static displayName = "Hello"; + static defaultProps = { /*...*/ }; + static propTypes = { /*...*/ }; +} +``` + +### When `static getter` is enabled: + +Examples of **incorrect** code for this rule: + +```js +class MyComponent extends React.Component { + static childContextTypes = { /*...*/ }; + static contextTypes = { /*...*/ }; + static contextType = { /*...*/ }; + static displayName = "Hello"; + static defaultProps = { /*...*/ }; + static propTypes = { /*...*/ }; +} +``` + +```js +class MyComponent extends React.Component { /*...*/ } +MyComponent.childContextTypes = { /*...*/ }; +MyComponent.contextTypes = { /*...*/ }; +MyComponent.contextType = { /*...*/ }; +MyComponent.displayName = "Hello"; +MyComponent.defaultProps = { /*...*/ }; +MyComponent.propTypes = { /*...*/ }; +``` + +Examples of **correct** code for this rule: + +```js +class MyComponent extends React.Component { + static get childContextTypes() { /*...*/ } + static get contextTypes() { /*...*/ } + static get contextType() { /*...*/ } + static get displayName() { /*...*/ } + static get defaultProps() { /*...*/ } + static get propTypes() { /*...*/ } +} +``` + +### When `property assignment` is enabled: + +Examples of **incorrect** code for this rule: + +```js +class MyComponent extends React.Component { + static childContextTypes = { /*...*/ }; + static contextTypes = { /*...*/ }; + static contextType = { /*...*/ }; + static displayName = "Hello"; + static defaultProps = { /*...*/ }; + static propTypes = { /*...*/ }; +} +``` + +```js +class MyComponent extends React.Component { + static get childContextTypes() { /*...*/ } + static get contextTypes() { /*...*/ } + static get contextType() { /*...*/ } + static get displayName() { /*...*/ } + static get defaultProps() { /*...*/ } + static get propTypes() { /*...*/ } +} +``` + +Examples of **correct** code for this rule: + +```js +class MyComponent extends React.Component { /*...*/ } +MyComponent.childContextTypes = { /*...*/ }; +MyComponent.contextTypes = { /*...*/ }; +MyComponent.contextType = { /*...*/ }; +MyComponent.displayName = "Hello"; +MyComponent.defaultProps = { /*...*/ }; +MyComponent.propTypes = { /*...*/ }; +``` + +### Options + +``` +... +"react/static-property-placement": [] // `static public field` enabled +... +``` + +or alternatively: + +``` +... +"react/static-property-placement": [, ] +... +``` + +or alternatively: + +``` +... +"react/static-property-placement": [, , { + childContextTypes: , + contextTypes: , + contextType: , + defaultProps: , + displayName: , + propTypes: , +}] +... +``` +The `` value must be one these options: +* `static public field` +* `static getter` +* `property assignment` + +The `options` schema defined above allows you to specify different rules for the different property fields available. + +##### Example configuration: +_This is only an example, we do not recommend this as a configuration._ +``` +... +"react/static-property-placement": ["warn", "property assignment", { + childContextTypes: "static getter", + contextTypes: "static public field", + contextType: "static public field", + displayName: "static public field", +}] +... +``` + +Based on the above configuration: +* `defaultProps` and `propTypes` will both enforce the `property assignment` rule. +* `childContextTypes` will enforce the `static getter` rule. +* `contextTypes`, `contextType`, and `displayName` will enforce the `static public field` rule. + +## When Not To Use It + +If you have no placement preference for React's static class properties. + diff --git a/index.js b/index.js index 79cf2d997a..b2d4ccdb90 100644 --- a/index.js +++ b/index.js @@ -82,6 +82,7 @@ const allRules = { 'sort-comp': require('./lib/rules/sort-comp'), 'sort-prop-types': require('./lib/rules/sort-prop-types'), 'state-in-constructor': require('./lib/rules/state-in-constructor'), + 'static-property-placement': require('./lib/rules/static-property-placement'), 'style-prop-object': require('./lib/rules/style-prop-object'), 'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children') }; diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js index fcd77895c6..97da4890a6 100644 --- a/lib/rules/display-name.js +++ b/lib/rules/display-name.js @@ -7,6 +7,7 @@ const Components = require('../util/Components'); const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); +const propsUtil = require('../util/props'); // ------------------------------------------------------------------------------ // Rule Definition @@ -38,24 +39,6 @@ module.exports = { const MISSING_MESSAGE = 'Component definition is missing display name'; - /** - * Checks if we are declaring a display name - * @param {ASTNode} node The AST node being checked. - * @returns {Boolean} True if we are declaring a display name, false if not. - */ - function isDisplayNameDeclaration(node) { - switch (node.type) { - case 'ClassProperty': - return node.key && node.key.name === 'displayName'; - case 'Identifier': - return node.name === 'displayName'; - case 'Literal': - return node.value === 'displayName'; - default: - return false; - } - } - /** * Mark a prop type as declared * @param {ASTNode} node The AST node being checked. @@ -139,14 +122,14 @@ module.exports = { return { ClassProperty: function(node) { - if (!isDisplayNameDeclaration(node)) { + if (!propsUtil.isDisplayNameDeclaration(node)) { return; } markDisplayNameAsDeclared(node); }, MemberExpression: function(node) { - if (!isDisplayNameDeclaration(node.property)) { + if (!propsUtil.isDisplayNameDeclaration(node.property)) { return; } const component = utils.getRelatedComponent(node); @@ -184,7 +167,7 @@ module.exports = { }, MethodDefinition: function(node) { - if (!isDisplayNameDeclaration(node.key)) { + if (!propsUtil.isDisplayNameDeclaration(node.key)) { return; } markDisplayNameAsDeclared(node); @@ -208,7 +191,7 @@ module.exports = { if (ignoreTranspilerName || !hasTranspilerName(node)) { // Search for the displayName declaration node.properties.forEach(property => { - if (!property.key || !isDisplayNameDeclaration(property.key)) { + if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) { return; } markDisplayNameAsDeclared(node); diff --git a/lib/rules/static-property-placement.js b/lib/rules/static-property-placement.js new file mode 100644 index 0000000000..19b3313f9b --- /dev/null +++ b/lib/rules/static-property-placement.js @@ -0,0 +1,152 @@ +/** + * @fileoverview Defines where React component static properties should be positioned. + * @author Daniel Mason + */ +'use strict'; + +const Components = require('../util/Components'); +const docsUrl = require('../util/docsUrl'); +const astUtil = require('../util/ast'); +const propsUtil = require('../util/props'); +const fromEntries = require('object.fromentries'); + +// ------------------------------------------------------------------------------ +// Positioning Options +// ------------------------------------------------------------------------------ +const STATIC_PUBLIC_FIELD = 'static public field'; +const STATIC_GETTER = 'static getter'; +const PROPERTY_ASSIGNMENT = 'property assignment'; +const POSITION_SETTINGS = [STATIC_PUBLIC_FIELD, STATIC_GETTER, PROPERTY_ASSIGNMENT]; + +// ------------------------------------------------------------------------------ +// Rule messages +// ------------------------------------------------------------------------------ +const ERROR_MESSAGES = { + [STATIC_PUBLIC_FIELD]: '\'{{name}}\' should be declared as a static class property.', + [STATIC_GETTER]: '\'{{name}}\' should be declared as a static getter class function.', + [PROPERTY_ASSIGNMENT]: '\'{{name}}\' should be declared outside the class body.' +}; + +// ------------------------------------------------------------------------------ +// Properties to check +// ------------------------------------------------------------------------------ +const propertiesToCheck = { + propTypes: propsUtil.isPropTypesDeclaration, + defaultProps: propsUtil.isDefaultPropsDeclaration, + childContextTypes: propsUtil.isChildContextTypesDeclaration, + contextTypes: propsUtil.isContextTypesDeclaration, + contextType: propsUtil.isContextTypeDeclaration, + displayName: node => propsUtil.isDisplayNameDeclaration(astUtil.getPropertyNameNode(node)) +}; + +const classProperties = Object.keys(propertiesToCheck); +const schemaProperties = fromEntries(classProperties.map(property => [property, {enum: POSITION_SETTINGS}])); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Defines where React component static properties should be positioned.', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('static-property-placement') + }, + fixable: null, // or 'code' or 'whitespace' + schema: [ + {enum: POSITION_SETTINGS}, + { + type: 'object', + properties: schemaProperties, + additionalProperties: false + } + ] + }, + + create: Components.detect((context, components, utils) => { + // variables should be defined here + const options = context.options; + const defaultCheckType = options[0] || STATIC_PUBLIC_FIELD; + const hasAdditionalConfig = options.length > 1; + const additionalConfig = hasAdditionalConfig ? options[1] : {}; + + // Set config + const config = fromEntries(classProperties.map(property => [ + property, + additionalConfig[property] || defaultCheckType + ])); + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + /** + * Checks if we are declaring function in class + * @returns {Boolean} True if we are declaring function in class, false if not. + */ + function isFunctionInClass () { + let blockNode; + let scope = context.getScope(); + while (scope) { + blockNode = scope.block; + if (blockNode && blockNode.type === 'ClassDeclaration') { + return true; + } + scope = scope.upper; + } + + return false; + } + + /** + * Check if we should report this property node + * @param node + * @param expectedRule + */ + function reportNodeIncorrectlyPositioned(node, expectedRule) { + // Detect if this node is an expected property declaration adn return the property name + const name = classProperties.find(propertyName => { + if (propertiesToCheck[propertyName](node)) { + return propertyName; + } + + return null; + }); + + // If name is set but the configured rule does not match expected then report error + if (name && config[name] !== expectedRule) { + // Report the error + context.report(node, ERROR_MESSAGES[config[name]], {name}); + } + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + return { + ClassProperty: node => reportNodeIncorrectlyPositioned(node, STATIC_PUBLIC_FIELD), + + MemberExpression: node => { + const relatedComponent = utils.getRelatedComponent(node); + + // Only check es6 components + if (!relatedComponent || !utils.isES6Component(relatedComponent.node)) { + return; + } + + // Report if needed + reportNodeIncorrectlyPositioned(node, PROPERTY_ASSIGNMENT); + }, + + MethodDefinition: node => { + // If the function is inside a class and is static getter then check if correctly positioned + if (isFunctionInClass() && node.static && node.kind === 'get') { + // Report error if needed + reportNodeIncorrectlyPositioned(node, STATIC_GETTER); + } + } + }; + }) +}; diff --git a/lib/util/props.js b/lib/util/props.js index f90648ccf2..66f6042fff 100644 --- a/lib/util/props.js +++ b/lib/util/props.js @@ -35,6 +35,15 @@ function isContextTypesDeclaration(node) { return astUtil.getPropertyName(node) === 'contextTypes'; } +/** + * Checks if the node passed in looks like a contextType declaration. + * @param {ASTNode} node The node to check. + * @returns {Boolean} `true` if the node is a contextType declaration, `false` if not + */ +function isContextTypeDeclaration(node) { + return astUtil.getPropertyName(node) === 'contextType'; +} + /** * Checks if the node passed in looks like a childContextTypes declaration. * @param {ASTNode} node The node to check. @@ -54,6 +63,24 @@ function isDefaultPropsDeclaration(node) { return (propName === 'defaultProps' || propName === 'getDefaultProps'); } +/** + * Checks if we are declaring a display name + * @param {node} node The AST node being checked. + * @returns {Boolean} True if we are declaring a display name, false if not. + */ +function isDisplayNameDeclaration(node) { + switch (node.type) { + case 'ClassProperty': + return node.key && node.key.name === 'displayName'; + case 'Identifier': + return node.name === 'displayName'; + case 'Literal': + return node.value === 'displayName'; + default: + return false; + } +} + /** * Checks if the PropTypes MemberExpression node passed in declares a required propType. * @param {ASTNode} propTypeExpression node to check. Must be a `PropTypes` MemberExpression. @@ -66,7 +93,9 @@ function isRequiredPropType(propTypeExpression) { module.exports = { isPropTypesDeclaration: isPropTypesDeclaration, isContextTypesDeclaration: isContextTypesDeclaration, + isContextTypeDeclaration: isContextTypeDeclaration, isChildContextTypesDeclaration: isChildContextTypesDeclaration, isDefaultPropsDeclaration: isDefaultPropsDeclaration, + isDisplayNameDeclaration: isDisplayNameDeclaration, isRequiredPropType: isRequiredPropType }; diff --git a/tests/lib/rules/static-property-placement.js b/tests/lib/rules/static-property-placement.js new file mode 100644 index 0000000000..e3123fef01 --- /dev/null +++ b/tests/lib/rules/static-property-placement.js @@ -0,0 +1,1797 @@ +/** + * @fileoverview Defines where React component static properties should be positioned. + * @author Daniel Mason + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Positioning Options +// ------------------------------------------------------------------------------ +const STATIC_PUBLIC_FIELD = 'static public field'; +const STATIC_GETTER = 'static getter'; +const PROPERTY_ASSIGNMENT = 'property assignment'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/static-property-placement'); +const RuleTester = require('eslint').RuleTester; + +const ruleTesterConfig = { + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } + }, + settings: { + react: { + version: '15' + } + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(ruleTesterConfig); +ruleTester.run('static-property-placement', rule, { + valid: [ + // ------------------------------------------------------------------------------ + // Ignore creatClass/createReactClass and Static Functional Components + // ------------------------------------------------------------------------------ + { + // Do not error on createReactClass pragma + code: [` + var MyComponent = createReactClass({ + childContextTypes: { + something: PropTypes.bool + }, + + contextTypes: { + something: PropTypes.bool + }, + + getDefaultProps: function() { + name: 'Bob' + }, + + displayName: 'Hello', + + propTypes: { + something: PropTypes.bool + }, + + render: function() { + return null; + }, + }); + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error on createClass pragma + code: [` + var MyComponent = React.createClass({ + childContextTypes: { + something: PropTypes.bool + }, + + contextTypes: { + something: PropTypes.bool + }, + + getDefaultProps: function() { + name: 'Bob' + }, + + displayName: 'Hello', + + propTypes: { + something: PropTypes.bool + }, + + render: function() { + return null; + }, + }); + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error on SFC arrow function with return + code: [` + const MyComponent = () => { + return
Hello
; + }; + + MyComponent.childContextTypes = { + something: PropTypes.bool + }; + + MyComponent.contextTypes = { + something: PropTypes.bool + }; + + MyComponent.defaultProps = { + something: 'Bob' + }; + + MyComponent.displayName = 'Hello'; + + MyComponent.propTypes = { + something: PropTypes.bool + }; + `].join('\n') + }, + { + // Do not error on SFC arrow function with direct return + code: [` + const MyComponent = () => (
Hello
); + + MyComponent.childContextTypes = { + something: PropTypes.bool + }; + + MyComponent.contextTypes = { + something: PropTypes.bool + }; + + MyComponent.defaultProps = { + something: 'Bob' + }; + + MyComponent.displayName = 'Hello'; + + MyComponent.propTypes = { + something: PropTypes.bool + }; + `].join('\n') + }, + { + // Do not error on SFC as unnamed function + code: [` + export function MyComponent () { + return
Hello
; + }; + + MyComponent.childContextTypes = { + something: PropTypes.bool + }; + + MyComponent.contextTypes = { + something: PropTypes.bool + }; + + MyComponent.defaultProps = { + something: 'Bob' + }; + + MyComponent.displayName = 'Hello'; + + MyComponent.propTypes = { + something: PropTypes.bool + }; + `].join('\n') + }, + // ------------------------------------------------------------------------------ + // no properties + // ------------------------------------------------------------------------------ + { + // Do not error if no properties defined + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + `].join('\n') + }, + { + // Do not error if unchecked properties defined + code: [` + class MyComponent extends React.Component { + static randomlyNamed = { + name: 'random' + } + } + `].join('\n') + }, + { + // Do not error if unchecked static properties defined and assignment rule enabled + code: [` + class MyComponent extends React.Component { + static randomlyNamed = { + name: 'random' + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if unchecked assignment properties defined and assignment rule enabled + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.randomlyNamed = { + name: 'random' + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if unchecked assignment properties defined and static rule enabled + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.randomlyNamed = { + name: 'random' + } + `].join('\n') + }, + // ------------------------------------------------------------------------------ + // childContextTypes - static field + // ------------------------------------------------------------------------------ + { + // Do not error if childContextTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + } + `].join('\n') + }, + { + // Do not error if childContextTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {childContextTypes: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // childContextTypes - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if childContextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {childContextTypes: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // childContextTypes - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if childContextTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if childContextTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {childContextTypes: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // contextTypes - static field + // ------------------------------------------------------------------------------ + { + // Do not error if contextTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static contextTypes = { + something: PropTypes.bool + }; + } + `].join('\n') + }, + { + // Do not error if contextTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static contextTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {contextTypes: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // contextTypes - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {contextTypes: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // contextTypes - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if contextTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if contextTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {contextTypes: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // contextType - static field + // ------------------------------------------------------------------------------ + { + // Do not error if contextType correctly defined - static field + code: [` + class MyComponent extends React.Component { + static contextType = MyContext; + } + `].join('\n') + }, + { + // Do not error if contextType correctly defined - static field + code: [` + class MyComponent extends React.Component { + static contextType = MyContext; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {contextType: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // contextType - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if contextType correctly defined - static field + code: [` + class MyComponent extends React.Component { + static get contextType() { + return MyContext; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextType correctly defined - static field + code: [` + class MyComponent extends React.Component { + static get contextType() { + return MyContext; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {contextType: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // contextType - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if contextType correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.contextType = MyContext; + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if contextType correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.contextType = MyContext; + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {contextType: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // displayName - static field + // ------------------------------------------------------------------------------ + { + // Do not error if displayName correctly defined - static field + code: [` + class MyComponent extends React.Component { + static displayName = "Hello"; + } + `].join('\n') + }, + { + // Do not error if displayName correctly defined - static field + code: [` + class MyComponent extends React.Component { + static displayName = "Hello"; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {displayName: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // displayName - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if displayName correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get displayName() { + return "Hello"; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get displayName() { + return "Hello"; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {displayName: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // displayName - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if displayName correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.displayName = "Hello"; + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if displayName correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.displayName = "Hello"; + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {displayName: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // defaultProps - static field + // ------------------------------------------------------------------------------ + { + // Do not error if defaultProps correctly defined - static field + code: [` + class MyComponent extends React.Component { + static defaultProps = { + something: 'Bob' + }; + } + `].join('\n') + }, + { + // Do not error if defaultProps correctly defined - static field + code: [` + class MyComponent extends React.Component { + static defaultProps = { + something: 'Bob' + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {defaultProps: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // defaultProps - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if defaultProps correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get defaultProps() { + return { + something: 'Bob' + }; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get defaultProps() { + return { + something: 'Bob' + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {defaultProps: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // defaultProps - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if defaultProps correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if defaultProps correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {defaultProps: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // propTypes - static field + // ------------------------------------------------------------------------------ + { + // Do not error if propTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n') + }, + { + // Do not error if propTypes correctly defined - static field + code: [` + class MyComponent extends React.Component { + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {propTypes: STATIC_PUBLIC_FIELD}] + }, + // ------------------------------------------------------------------------------ + // propTypes - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if propTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, {propTypes: STATIC_GETTER}] + }, + // ------------------------------------------------------------------------------ + // propTypes - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if propTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if propTypes correctly defined - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, {propTypes: PROPERTY_ASSIGNMENT}] + }, + // ------------------------------------------------------------------------------ + // multiple - static field + // ------------------------------------------------------------------------------ + { + // Do not error if multiple properties and match config - static field + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n') + }, + { + // Do not error if multiple properties and match config - static field + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_PUBLIC_FIELD, + contextTypes: STATIC_PUBLIC_FIELD, + contextType: STATIC_PUBLIC_FIELD, + displayName: STATIC_PUBLIC_FIELD, + defaultProps: STATIC_PUBLIC_FIELD, + propTypes: STATIC_PUBLIC_FIELD + }] + }, + // ------------------------------------------------------------------------------ + // multiple - static getter + // ------------------------------------------------------------------------------ + { + // Do not error if childContextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [STATIC_GETTER] + }, + { + // Do not error if contextTypes correctly defined - static getter + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_GETTER, + contextTypes: STATIC_GETTER, + contextType: STATIC_GETTER, + displayName: STATIC_GETTER, + defaultProps: STATIC_GETTER, + propTypes: STATIC_GETTER + }] + }, + // ------------------------------------------------------------------------------ + // multiple - assignment + // ------------------------------------------------------------------------------ + { + // Do not error if multiple properties and match config - assignment + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT] + }, + { + // Do not error if multiple properties and match config - static field + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, { + childContextTypes: PROPERTY_ASSIGNMENT, + contextTypes: PROPERTY_ASSIGNMENT, + displayName: PROPERTY_ASSIGNMENT, + defaultProps: PROPERTY_ASSIGNMENT, + propTypes: PROPERTY_ASSIGNMENT + }] + }, + // ------------------------------------------------------------------------------ + // combined - mixed + // ------------------------------------------------------------------------------ + { + // Do not error if mixed property positions and match config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static get displayName() { + return "Hello" + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, { + displayName: STATIC_GETTER, + defaultProps: PROPERTY_ASSIGNMENT, + propTypes: PROPERTY_ASSIGNMENT + }] + }, + { + // Do not error if mixed property positions and match config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static get displayName() { + return "Hello" + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_PUBLIC_FIELD, + contextTypes: STATIC_PUBLIC_FIELD, + displayName: STATIC_GETTER + }] + }, + // ------------------------------------------------------------------------------ + // mixed component types + // ------------------------------------------------------------------------------ + { + // SFC ignored and component is valid + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static displayName = "Hello"; + } + + const OtherComponent = () => (
Hello
); + + OtherComponent.defaultProps = { + name: 'Bob' + } + + OtherComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n') + }, + { + // Multiple components validated + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static displayName = "Hello"; + } + + class OtherComponent extends React.Component { + static defaultProps = { + name: 'Bob' + } + + static propTypes = { + name: PropTypes.string.isRequired + } + } + `].join('\n') + } + ], + + invalid: [ + // ------------------------------------------------------------------------------ + // expected static field when got property assigment + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextType = MyContext; + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + errors: [ + {message: '\'childContextTypes\' should be declared as a static class property.'}, + {message: '\'contextTypes\' should be declared as a static class property.'}, + {message: '\'contextType\' should be declared as a static class property.'}, + {message: '\'displayName\' should be declared as a static class property.'}, + {message: '\'defaultProps\' should be declared as a static class property.'}, + {message: '\'propTypes\' should be declared as a static class property.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextType = MyContext; + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_PUBLIC_FIELD, + contextTypes: STATIC_PUBLIC_FIELD, + contextType: STATIC_PUBLIC_FIELD, + displayName: STATIC_PUBLIC_FIELD, + defaultProps: STATIC_PUBLIC_FIELD, + propTypes: STATIC_PUBLIC_FIELD + }], + errors: [ + {message: '\'childContextTypes\' should be declared as a static class property.'}, + {message: '\'contextTypes\' should be declared as a static class property.'}, + {message: '\'contextType\' should be declared as a static class property.'}, + {message: '\'displayName\' should be declared as a static class property.'}, + {message: '\'defaultProps\' should be declared as a static class property.'}, + {message: '\'propTypes\' should be declared as a static class property.'} + ] + }, + // ------------------------------------------------------------------------------ + // expected static field when got static getter + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + errors: [ + {message: '\'childContextTypes\' should be declared as a static class property.'}, + {message: '\'contextTypes\' should be declared as a static class property.'}, + {message: '\'contextType\' should be declared as a static class property.'}, + {message: '\'displayName\' should be declared as a static class property.'}, + {message: '\'defaultProps\' should be declared as a static class property.'}, + {message: '\'propTypes\' should be declared as a static class property.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_PUBLIC_FIELD, + contextTypes: STATIC_PUBLIC_FIELD, + contextType: STATIC_PUBLIC_FIELD, + displayName: STATIC_PUBLIC_FIELD, + defaultProps: STATIC_PUBLIC_FIELD, + propTypes: STATIC_PUBLIC_FIELD + }], + errors: [ + {message: '\'childContextTypes\' should be declared as a static class property.'}, + {message: '\'contextTypes\' should be declared as a static class property.'}, + {message: '\'contextType\' should be declared as a static class property.'}, + {message: '\'displayName\' should be declared as a static class property.'}, + {message: '\'defaultProps\' should be declared as a static class property.'}, + {message: '\'propTypes\' should be declared as a static class property.'} + ] + }, + // ------------------------------------------------------------------------------ + // expected property assignment when got static field + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared outside the class body.'}, + {message: '\'propTypes\' should be declared outside the class body.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, { + childContextTypes: PROPERTY_ASSIGNMENT, + contextTypes: PROPERTY_ASSIGNMENT, + contextType: PROPERTY_ASSIGNMENT, + displayName: PROPERTY_ASSIGNMENT, + defaultProps: PROPERTY_ASSIGNMENT, + propTypes: PROPERTY_ASSIGNMENT + }], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared outside the class body.'}, + {message: '\'propTypes\' should be declared outside the class body.'} + ] + }, + // ------------------------------------------------------------------------------ + // expected property assignment when got static getter + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared outside the class body.'}, + {message: '\'propTypes\' should be declared outside the class body.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static get childContextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextTypes() { + return { + something: PropTypes.bool + }; + } + + static get contextType() { + return MyContext; + } + + static get displayName() { + return "Hello"; + } + + static get defaultProps() { + return { + something: PropTypes.bool + }; + } + + static get propTypes() { + return { + something: PropTypes.bool + }; + } + } + `].join('\n'), + options: [STATIC_GETTER, { + childContextTypes: PROPERTY_ASSIGNMENT, + contextTypes: PROPERTY_ASSIGNMENT, + contextType: PROPERTY_ASSIGNMENT, + displayName: PROPERTY_ASSIGNMENT, + defaultProps: PROPERTY_ASSIGNMENT, + propTypes: PROPERTY_ASSIGNMENT + }], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared outside the class body.'}, + {message: '\'propTypes\' should be declared outside the class body.'} + ] + }, + // ------------------------------------------------------------------------------ + // expected static getter when got static field + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [STATIC_GETTER], + errors: [ + {message: '\'childContextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextType\' should be declared as a static getter class function.'}, + {message: '\'displayName\' should be declared as a static getter class function.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static getter class function.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + something: PropTypes.bool + }; + + static contextTypes = { + something: PropTypes.bool + }; + + static contextType = MyContext; + + static displayName = "Hello"; + + static defaultProps = { + something: 'Bob' + }; + + static propTypes = { + something: PropTypes.bool + }; + } + `].join('\n'), + options: [STATIC_PUBLIC_FIELD, { + childContextTypes: STATIC_GETTER, + contextTypes: STATIC_GETTER, + contextType: STATIC_GETTER, + displayName: STATIC_GETTER, + defaultProps: STATIC_GETTER, + propTypes: STATIC_GETTER + }], + errors: [ + {message: '\'childContextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextType\' should be declared as a static getter class function.'}, + {message: '\'displayName\' should be declared as a static getter class function.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static getter class function.'} + ] + }, + // ------------------------------------------------------------------------------ + // expected static getter when got property assignment + // ------------------------------------------------------------------------------ + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextType = MyContext; + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_GETTER], + errors: [ + {message: '\'childContextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextType\' should be declared as a static getter class function.'}, + {message: '\'displayName\' should be declared as a static getter class function.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static getter class function.'} + ] + }, + { + // Error if multiple properties are incorrectly positioned according to config + code: [` + class MyComponent extends React.Component { + render() { + return null; + } + } + + MyComponent.childContextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextTypes = { + name: PropTypes.string.isRequired + } + + MyComponent.contextType = MyContext; + + MyComponent.displayName = "Hello"; + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + childContextTypes: STATIC_GETTER, + contextTypes: STATIC_GETTER, + contextType: STATIC_GETTER, + displayName: STATIC_GETTER, + defaultProps: STATIC_GETTER, + propTypes: STATIC_GETTER + }], + errors: [ + {message: '\'childContextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextTypes\' should be declared as a static getter class function.'}, + {message: '\'contextType\' should be declared as a static getter class function.'}, + {message: '\'displayName\' should be declared as a static getter class function.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static getter class function.'} + ] + }, + // ------------------------------------------------------------------------------ + // combined - mixed + // ------------------------------------------------------------------------------ + { + // Error if mixed property positions but dont match config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static contextType = MyContext; + + static get displayName() { + return "Hello"; + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + defaultProps: STATIC_GETTER, + propTypes: STATIC_PUBLIC_FIELD, + displayName: STATIC_PUBLIC_FIELD + }], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared as a static class property.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static class property.'} + ] + }, + { + // Error if mixed property positions but dont match config + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static contextType = MyContext; + + static get displayName() { + return "Hello"; + } + } + + MyComponent.defaultProps = { + name: 'Bob' + } + + MyComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [STATIC_GETTER, { + childContextTypes: PROPERTY_ASSIGNMENT, + contextTypes: PROPERTY_ASSIGNMENT, + contextType: PROPERTY_ASSIGNMENT, + displayName: PROPERTY_ASSIGNMENT + }], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared as a static getter class function.'}, + {message: '\'propTypes\' should be declared as a static getter class function.'} + ] + }, + // ------------------------------------------------------------------------------ + // mixed component types + // ------------------------------------------------------------------------------ + { + // SFC ignored and component is invalid + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static contextType = MyContext; + + static get displayName() { + return "Hello"; + } + } + + const OtherComponent = () => (
Hello
); + + OtherComponent.defaultProps = { + name: 'Bob' + } + + OtherComponent.propTypes = { + name: PropTypes.string.isRequired + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT, { + defaultProps: STATIC_PUBLIC_FIELD, + propTypes: STATIC_GETTER + }], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'} + ] + }, + { + // Multiple components validated + code: [` + class MyComponent extends React.Component { + static childContextTypes = { + name: PropTypes.string.isRequired + } + + static contextTypes = { + name: PropTypes.string.isRequired + } + + static contextType = MyContext; + + static displayName = "Hello"; + } + + class OtherComponent extends React.Component { + static contextTypes = { + name: PropTypes.string.isRequired + } + + static defaultProps = { + name: 'Bob' + } + + static propTypes = { + name: PropTypes.string.isRequired + } + + static get displayName() { + return "Hello"; + } + } + `].join('\n'), + options: [PROPERTY_ASSIGNMENT], + errors: [ + {message: '\'childContextTypes\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'contextType\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'}, + {message: '\'contextTypes\' should be declared outside the class body.'}, + {message: '\'defaultProps\' should be declared outside the class body.'}, + {message: '\'propTypes\' should be declared outside the class body.'}, + {message: '\'displayName\' should be declared outside the class body.'} + ] + + } + ] +});