diff --git a/docs/rules/sort-comp.md b/docs/rules/sort-comp.md index 87612f115d..709ed55e43 100644 --- a/docs/rules/sort-comp.md +++ b/docs/rules/sort-comp.md @@ -89,9 +89,11 @@ The default configuration is: * `lifecycle` is referring to the `lifecycle` group defined in `groups`. * `everything-else` is a special group that match all the methods that do not match any of the other groups. * `render` is referring to the `render` method. -* `type-annotations`. This group is not specified by default, but can be used to enforce flow annotations to be at the top. +* `type-annotations`. This group is not specified by default, but can be used to enforce flow annotations positioning. * `getters` This group is not specified by default, but can be used to enforce class getters positioning. * `setters` This group is not specified by default, but can be used to enforce class setters positioning. +* `instance-variables` This group is not specified by default, but can be used to enforce all other instance variables positioning. +* `instance-methods` This group is not specified by default, but can be used to enforce all other instance methods positioning. You can override this configuration to match your needs. diff --git a/lib/rules/sort-comp.js b/lib/rules/sort-comp.js index 95d40e6dce..1afd10695b 100644 --- a/lib/rules/sort-comp.js +++ b/lib/rules/sort-comp.js @@ -172,6 +172,20 @@ module.exports = { } } + if (indexes.length === 0 && method.instanceVariable) { + const annotationIndex = methodsOrder.indexOf('instance-variables'); + if (annotationIndex >= 0) { + indexes.push(annotationIndex); + } + } + + if (indexes.length === 0 && method.instanceMethod) { + const annotationIndex = methodsOrder.indexOf('instance-methods'); + if (annotationIndex >= 0) { + indexes.push(annotationIndex); + } + } + // No matching pattern, return 'everything-else' index if (indexes.length === 0) { for (i = 0, j = methodsOrder.length; i < j; i++) { @@ -361,6 +375,16 @@ module.exports = { getter: node.kind === 'get', setter: node.kind === 'set', static: node.static, + instanceVariable: !node.static && + node.type === 'ClassProperty' && + node.value && + node.value.type !== 'ArrowFunctionExpression' && + node.value.type !== 'FunctionExpression', + instanceMethod: !node.static && + node.type === 'ClassProperty' && + node.value && + (node.value.type === 'ArrowFunctionExpression' || + node.value.type === 'FunctionExpression'), typeAnnotation: !!node.typeAnnotation && node.value === null })); diff --git a/tests/lib/rules/sort-comp.js b/tests/lib/rules/sort-comp.js index df1944040e..ac9da200a3 100644 --- a/tests/lib/rules/sort-comp.js +++ b/tests/lib/rules/sort-comp.js @@ -310,6 +310,50 @@ ruleTester.run('sort-comp', rule, { 'render' ] }] + }, { + // Instance methods should be at the top + code: [ + 'class Hello extends React.Component {', + ' foo = () => {}', + ' constructor() {}', + ' classMethod() {}', + ' static bar = () => {}', + ' render() {', + ' return
{this.props.text}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + order: [ + 'instance-methods', + 'lifecycle', + 'everything-else', + 'render' + ] + }] + }, { + // Instance variables should be at the top + code: [ + 'class Hello extends React.Component {', + ' foo = \'bar\'', + ' constructor() {}', + ' state = {}', + ' static bar = \'foo\'', + ' render() {', + ' return
{this.props.text}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + order: [ + 'instance-variables', + 'lifecycle', + 'everything-else', + 'render' + ] + }] }], invalid: [{ @@ -515,5 +559,51 @@ ruleTester.run('sort-comp', rule, { 'render' ] }] + }, { + // Instance methods should not be at the top + code: [ + 'class Hello extends React.Component {', + ' constructor() {}', + ' static bar = () => {}', + ' classMethod() {}', + ' foo = function() {}', + ' render() {', + ' return
{this.props.text}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{message: 'foo should be placed before constructor'}], + options: [{ + order: [ + 'instance-methods', + 'lifecycle', + 'everything-else', + 'render' + ] + }] + }, { + // Instance variables should not be at the top + code: [ + 'class Hello extends React.Component {', + ' constructor() {}', + ' state = {}', + ' static bar = {}', + ' foo = {}', + ' render() {', + ' return
{this.props.text}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{message: 'foo should be placed before constructor'}], + options: [{ + order: [ + 'instance-variables', + 'lifecycle', + 'everything-else', + 'render' + ] + }] }] });