From e31280c85dabab50604128ad846f577755fcc8c3 Mon Sep 17 00:00:00 2001 From: Dmitry Korlas Date: Tue, 29 Nov 2022 20:01:11 +0400 Subject: [PATCH 1/2] move sort-comp fixtures to a separate folder --- .../{ => sort-comp}/sort-comp-pure.input.js | 0 .../{ => sort-comp}/sort-comp-pure.output.js | 0 .../{ => sort-comp}/sort-comp.input.js | 0 .../{ => sort-comp}/sort-comp.output.js | 0 .../{ => sort-comp}/sort-comp2.input.js | 0 .../{ => sort-comp}/sort-comp2.output.js | 0 .../{ => sort-comp}/sort-comp3.input.js | 0 .../{ => sort-comp}/sort-comp3.output.js | 0 transforms/__tests__/sort-comp-test.js | 10 +++++----- 9 files changed, 5 insertions(+), 5 deletions(-) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp-pure.input.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp-pure.output.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp.input.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp.output.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp2.input.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp2.output.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp3.input.js (100%) rename transforms/__testfixtures__/{ => sort-comp}/sort-comp3.output.js (100%) diff --git a/transforms/__testfixtures__/sort-comp-pure.input.js b/transforms/__testfixtures__/sort-comp/sort-comp-pure.input.js similarity index 100% rename from transforms/__testfixtures__/sort-comp-pure.input.js rename to transforms/__testfixtures__/sort-comp/sort-comp-pure.input.js diff --git a/transforms/__testfixtures__/sort-comp-pure.output.js b/transforms/__testfixtures__/sort-comp/sort-comp-pure.output.js similarity index 100% rename from transforms/__testfixtures__/sort-comp-pure.output.js rename to transforms/__testfixtures__/sort-comp/sort-comp-pure.output.js diff --git a/transforms/__testfixtures__/sort-comp.input.js b/transforms/__testfixtures__/sort-comp/sort-comp.input.js similarity index 100% rename from transforms/__testfixtures__/sort-comp.input.js rename to transforms/__testfixtures__/sort-comp/sort-comp.input.js diff --git a/transforms/__testfixtures__/sort-comp.output.js b/transforms/__testfixtures__/sort-comp/sort-comp.output.js similarity index 100% rename from transforms/__testfixtures__/sort-comp.output.js rename to transforms/__testfixtures__/sort-comp/sort-comp.output.js diff --git a/transforms/__testfixtures__/sort-comp2.input.js b/transforms/__testfixtures__/sort-comp/sort-comp2.input.js similarity index 100% rename from transforms/__testfixtures__/sort-comp2.input.js rename to transforms/__testfixtures__/sort-comp/sort-comp2.input.js diff --git a/transforms/__testfixtures__/sort-comp2.output.js b/transforms/__testfixtures__/sort-comp/sort-comp2.output.js similarity index 100% rename from transforms/__testfixtures__/sort-comp2.output.js rename to transforms/__testfixtures__/sort-comp/sort-comp2.output.js diff --git a/transforms/__testfixtures__/sort-comp3.input.js b/transforms/__testfixtures__/sort-comp/sort-comp3.input.js similarity index 100% rename from transforms/__testfixtures__/sort-comp3.input.js rename to transforms/__testfixtures__/sort-comp/sort-comp3.input.js diff --git a/transforms/__testfixtures__/sort-comp3.output.js b/transforms/__testfixtures__/sort-comp/sort-comp3.output.js similarity index 100% rename from transforms/__testfixtures__/sort-comp3.output.js rename to transforms/__testfixtures__/sort-comp/sort-comp3.output.js diff --git a/transforms/__tests__/sort-comp-test.js b/transforms/__tests__/sort-comp-test.js index bdeb3447..0bfd4e10 100644 --- a/transforms/__tests__/sort-comp-test.js +++ b/transforms/__tests__/sort-comp-test.js @@ -2,14 +2,14 @@ * Copyright 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * LICENSE file in the root directory of this source tree. * */ 'use strict'; const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'sort-comp'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp2'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp3'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp-pure'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp2'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp3'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-pure'); From af798a0d4e95cd8f1c495777c447cc3d4e4b7645 Mon Sep 17 00:00:00 2001 From: Dmitry Korlas Date: Fri, 2 Dec 2022 12:59:13 +0400 Subject: [PATCH 2/2] Support method order declarations --- .../sort-comp-default-props.input.js | 37 +++++ .../sort-comp-default-props.output.js | 37 +++++ .../sort-comp/sort-comp-full.input.js | 76 +++++++++ .../sort-comp/sort-comp-full.output.js | 76 +++++++++ .../sort-comp-getters-setters.input.js | 54 ++++++ .../sort-comp-getters-setters.output.js | 54 ++++++ .../sort-comp-instance-vars.input.js | 43 +++++ .../sort-comp-instance-vars.output.js | 41 +++++ transforms/__tests__/sort-comp-test.js | 4 + transforms/sort-comp.js | 155 ++++++++++++++---- 10 files changed, 541 insertions(+), 36 deletions(-) create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-default-props.input.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-default-props.output.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-full.input.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-full.output.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.input.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.output.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.input.js create mode 100644 transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.output.js diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-default-props.input.js b/transforms/__testfixtures__/sort-comp/sort-comp-default-props.input.js new file mode 100644 index 00000000..6f33cf57 --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-default-props.input.js @@ -0,0 +1,37 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.Component { + componentDidMount() { + + } + + static defaultProps = { + foo: 42, + }; + + // comment related to render method + // this will be attached to render method + render() { + return
; + } + + // comment on constructor + constructor(props, context) { + super(props, context); + } + + static someStaticThing() { + // should bundle with other statics + } + + // should be before defaultProps + static propTypes = { + + }; + + myOwnMethod(foo) { + // comment within method + } +} + diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-default-props.output.js b/transforms/__testfixtures__/sort-comp/sort-comp-default-props.output.js new file mode 100644 index 00000000..f2958d8f --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-default-props.output.js @@ -0,0 +1,37 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.Component { + static someStaticThing() { + // should bundle with other statics + } + + // should be before defaultProps + static propTypes = { + + }; + + static defaultProps = { + foo: 42, + }; + + // comment on constructor + constructor(props, context) { + super(props, context); + } + + componentDidMount() { + + } + + myOwnMethod(foo) { + // comment within method + } + + // comment related to render method + // this will be attached to render method + render() { + return
; + } +} + diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-full.input.js b/transforms/__testfixtures__/sort-comp/sort-comp-full.input.js new file mode 100644 index 00000000..9705601e --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-full.input.js @@ -0,0 +1,76 @@ +import React from 'react'; + +export class SortCompExample extends React.Component { + model = new Date(); + + static y = 8; + + static x = 42; + + static propTypes = {}; + + componentDidMount() { + console.log('componentDidMount'); + } + + constructor(props, context) { + super(props, context); + + console.log(this); + } + + static defaultProps = { + disabled: false, + }; + + someMethod2 = () => {}; + + state = { + isDataReady: false, + }; + + onBar() {} + + handleBar = () => { + }; + + set foo(value) { + this.xx = value; + } + + handleBar2() {} + + handleFoo() {} + + handleFoo1 = () => {}; + + onBar1 = () => {}; + + renderFoo() {} + + onFoo() {} + + onFoo1 = () => {}; + + get foo() { + return 42; + } + + static track() {} + + setRef = (comp) => { + this.panelRef = comp; + }; + + methodFoo = () => {}; + + someMethod1() { + return 42; + } + + renderBar() {} + + render() { + return (
X
); + } +} diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-full.output.js b/transforms/__testfixtures__/sort-comp/sort-comp-full.output.js new file mode 100644 index 00000000..6cc3ca59 --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-full.output.js @@ -0,0 +1,76 @@ +import React from 'react'; + +export class SortCompExample extends React.Component { + static x = 42; + + static y = 8; + + static track() {} + + model = new Date(); + + static propTypes = {}; + + static defaultProps = { + disabled: false, + }; + + constructor(props, context) { + super(props, context); + + console.log(this); + } + + state = { + isDataReady: false, + }; + + componentDidMount() { + console.log('componentDidMount'); + } + + handleBar = () => { + }; + + handleBar2() {} + + handleFoo() {} + + handleFoo1 = () => {}; + + onBar() {} + + onBar1 = () => {}; + + onFoo() {} + + onFoo1 = () => {}; + + get foo() { + return 42; + } + + set foo(value) { + this.xx = value; + } + + setRef = (comp) => { + this.panelRef = comp; + }; + + methodFoo = () => {}; + + someMethod2 = () => {}; + + someMethod1() { + return 42; + } + + renderBar() {} + + renderFoo() {} + + render() { + return (
X
); + } +} diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.input.js b/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.input.js new file mode 100644 index 00000000..31c5ea0c --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.input.js @@ -0,0 +1,54 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.PureComponent { + // comment related to render method + // this will be attached to render method + render() { + return
; + } + + // comment on componentDidMount + componentDidMount() { + } + + // foo getter comment + get foo() { + return 42; + } + + set foo(value) { + this.instanceVariable = value; + } + + static someStaticThing() { + // should bundle with other statics + } + + get bar() { + + } + + set bar(value) { + + } + + renderFoo() { + // other render* function + } + + instanceVariable; + + renderBar() { + // should come before renderFoo + } + + static aStaticThing() { + // should come first + } + + myOwnMethod(foo) { + // comment within method + } +} + diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.output.js b/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.output.js new file mode 100644 index 00000000..2864745a --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-getters-setters.output.js @@ -0,0 +1,54 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.PureComponent { + static aStaticThing() { + // should come first + } + + static someStaticThing() { + // should bundle with other statics + } + + instanceVariable; + + // comment on componentDidMount + componentDidMount() { + } + + get bar() { + + } + + // foo getter comment + get foo() { + return 42; + } + + set bar(value) { + + } + + set foo(value) { + this.instanceVariable = value; + } + + myOwnMethod(foo) { + // comment within method + } + + renderBar() { + // should come before renderFoo + } + + renderFoo() { + // other render* function + } + + // comment related to render method + // this will be attached to render method + render() { + return
; + } +} + diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.input.js b/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.input.js new file mode 100644 index 00000000..20428d25 --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.input.js @@ -0,0 +1,43 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.Component { + + // comment related to render method + // this will be attached to render method + render() { + return
; + } + + // comment on componentDidMount + componentDidMount() { + } + + static someStaticThing() { + // should bundle with other statics + } + + renderFoo() { + // other render* function + } + + foo; + + bar = null; + + renderBar() { + // should come before renderFoo + } + + model = new Date(); + + static aStaticThing() { + // should come first + } + + myOwnMethod(foo) { + // comment within method + } + +} + diff --git a/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.output.js b/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.output.js new file mode 100644 index 00000000..86d77463 --- /dev/null +++ b/transforms/__testfixtures__/sort-comp/sort-comp-instance-vars.output.js @@ -0,0 +1,41 @@ +import React from 'react/addons'; + +// comment above class +export class MyComponent extends React.Component { + static aStaticThing() { + // should come first + } + + static someStaticThing() { + // should bundle with other statics + } + + bar = null; + + foo; + + model = new Date(); + + // comment on componentDidMount + componentDidMount() { + } + + myOwnMethod(foo) { + // comment within method + } + + renderBar() { + // should come before renderFoo + } + + renderFoo() { + // other render* function + } + + // comment related to render method + // this will be attached to render method + render() { + return
; + } +} + diff --git a/transforms/__tests__/sort-comp-test.js b/transforms/__tests__/sort-comp-test.js index 0bfd4e10..1d6f0b59 100644 --- a/transforms/__tests__/sort-comp-test.js +++ b/transforms/__tests__/sort-comp-test.js @@ -13,3 +13,7 @@ defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp'); defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp2'); defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp3'); defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-pure'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-instance-vars'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-getters-setters'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-default-props'); +defineTest(__dirname, 'sort-comp', null, 'sort-comp/sort-comp-full'); diff --git a/transforms/sort-comp.js b/transforms/sort-comp.js index 6e8adf61..e94d9ab2 100644 --- a/transforms/sort-comp.js +++ b/transforms/sort-comp.js @@ -12,15 +12,21 @@ * Reorders React component methods to match the [ESLint](http://eslint.org/) * [react/sort-comp rule](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md), * specifically with the [tighter constraints of the Airbnb style guide] - * (https://github.com/airbnb/javascript/blob/7684892951ef663e1c4e62ad57d662e9b2748b9e/\ - * packages/eslint-config-airbnb/rules/react.js#L122-L134), + * (https://github.com/airbnb/javascript/blob/eslint-config-airbnb-v19.0.4/\ + * packages/eslint-config-airbnb/rules/react.js#L243-L256), * * 'react/sort-comp': [2, { * 'order': [ + * 'static-variables', * 'static-methods', + * 'instance-variables', * 'lifecycle', + * '/^handle.+$/', * '/^on.+$/', + * 'getters', + * 'setters', * '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', + * 'instance-methods', * 'everything-else', * '/^render.+$/', * 'render' @@ -52,8 +58,8 @@ module.exports = function(fileInfo, api, options) { const sameLocation = indexA === indexB; if (sameLocation) { - // compare lexically - return +(nameA > nameB) || +(nameA === nameB) - 1; + // compare lexically. + return nameA.localeCompare(nameB); } else { // compare by index return indexA - indexB; @@ -101,9 +107,9 @@ module.exports = function(fileInfo, api, options) { return null; }; -// Hard-coded for Airbnb style -const defaultMethodsOrder = [ - 'static-methods', +const REACT_LIFECYCLE_PLACEHOLDER = 'lifecycle'; + +const REACT_LIFECYCLE_METHODS = [ 'displayName', 'propTypes', 'contextTypes', @@ -129,8 +135,20 @@ const defaultMethodsOrder = [ 'componentDidUpdate', 'componentDidCatch', 'componentWillUnmount', +]; + +// Hard-coded for Airbnb style +const defaultMethodsOrder = [ + 'static-variables', + 'static-methods', + 'instance-variables', + REACT_LIFECYCLE_PLACEHOLDER, + '/^handle.+$/', '/^on.+$/', + 'getters', + 'setters', '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', + 'instance-methods', 'everything-else', '/^render.+$/', 'render' @@ -139,33 +157,7 @@ const defaultMethodsOrder = [ // FROM https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/sort-comp.js const regExpRegExp = /\/(.*)\/([g|y|i|m]*)/; -function selectorMatches(selector, method) { - const methodName = method.key.name; - - if ( - method.static && - selector === 'static-methods' && - defaultMethodsOrder.indexOf(methodName) === -1 - ) { - return true; - } - - if ( - !method.value && - method.typeAnnotation && - selector === 'type-annotations' - ) { - return true; - } - - if (method.static && selector === 'static-methods') { - return true; - } - - if (selector === methodName) { - return true; - } - +function isSelectorMatchesRegexp(selector, methodName) { const selectorIsRe = regExpRegExp.test(selector); if (selectorIsRe) { @@ -177,16 +169,94 @@ function selectorMatches(selector, method) { return false; } +function isSelectorMatchesClassDeclarations(selector, method) { + switch (selector) { + case 'static-variables': { + return ( + method.static && + method.type === 'ClassProperty' && + method.value?.type !== 'ArrowFunctionExpression' && + method.value?.type !== 'FunctionExpression' + ); + } + case 'static-methods': { + return ( + method.static && + ( + (method.type === 'ClassMethod' && !method.value) || + (method.type === 'ClassProperty' && + method.value?.type === 'ArrowFunctionExpression' || + method.value?.type === 'FunctionExpression' + ) + ) + ); + } + case 'type-annotations': { + return !method.value && method.typeAnnotation; + } + case 'instance-variables': { + return ( + !method.static && + method.type === 'ClassProperty' && + method.value?.type !== 'ArrowFunctionExpression' + ); + } + case 'instance-methods': { + return ( + !method.static && + method.type === 'ClassProperty' && + method.value?.type === 'ArrowFunctionExpression' + ); + } + case 'getters': { + return method.kind === 'get'; + } + case 'setters': { + return method.kind === 'set'; + } + default: { + return false; + } + } +} + /** * Get index of the matching patterns in methods order configuration + * @param {String[]} methodsOrder * @param {Object} method * @returns {Number} Index of the method in the method ordering. Return [Infinity] if there is no match. */ function getCorrectIndex(methodsOrder, method) { + const methodName = method.key.name; + + /* + The same method could be matched by several criteria, say `static defaultProps = {}` can be matched + by static-variables and exact name criteria. + Here we introduce a priority to keep them organized. + + priority: + 1 - match by exact name, say render, constructor, defaultProps, etc. + 2 - match by regexp + 3 - match by class declarations like static-variables, instance-methods etc. + 4 - match by everything-else + 5 - Infinity + */ + + const methodIndex = methodsOrder.indexOf(methodName); + if (methodIndex >= 0) { + return methodIndex; + } + + const matchedRegexpIndex = methodsOrder.findIndex(selector => isSelectorMatchesRegexp(selector, methodName)); + + if (matchedRegexpIndex >= 0) { + return matchedRegexpIndex; + } + const everythingElseIndex = methodsOrder.indexOf('everything-else'); for (let i = 0; i < methodsOrder.length; i++) { - if (i != everythingElseIndex && selectorMatches(methodsOrder[i], method)) { + if (i !== everythingElseIndex && isSelectorMatchesClassDeclarations(methodsOrder[i], method)) { return i; } } @@ -230,10 +300,23 @@ function getMethodsOrderFromEslint(filePath) { return null; } +function expandArray(arr, token, variants) { + return arr.reduce((acc, item) => { + if (item === token) { + return [...acc, ...variants]; + } + + acc.push(item); + return acc; + }, []); +} + function getMethodsOrder(fileInfo, options) { - return ( + const methodsOrders = ( options.methodsOrder || getMethodsOrderFromEslint(fileInfo.path) || defaultMethodsOrder ); + + return expandArray(methodsOrders, REACT_LIFECYCLE_PLACEHOLDER, REACT_LIFECYCLE_METHODS); }