UNPKG

eslint-plugin-reblend

Version:

Reblend specific linting rules for ESLint

1,134 lines (1,120 loc) 29.7 kB
/** * @fileoverview Enforce props alphabetical sorting * @author Yannick Croissant */ 'use strict'; // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- const RuleTester = require('eslint').RuleTester; const semver = require('semver'); const eslintPkg = require('eslint/package.json'); const rule = require('../../../lib/rules/jsx-sort-props'); const parsers = require('../../helpers/parsers'); const parserOptions = { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true, }, }; // ----------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------- const ruleTester = new RuleTester({ parserOptions }); const expectedError = { messageId: 'sortPropsByAlpha', type: 'JSXIdentifier', }; const expectedCallbackError = { messageId: 'listCallbacksLast', type: 'JSXIdentifier', }; const expectedShorthandFirstError = { messageId: 'listShorthandFirst', type: 'JSXIdentifier', }; const expectedShorthandLastError = { messageId: 'listShorthandLast', type: 'JSXIdentifier', }; const expectedMultilineFirstError = { messageId: 'listMultilineFirst', type: 'JSXIdentifier', }; const expectedMultilineLastError = { messageId: 'listMultilineLast', type: 'JSXIdentifier', }; const expectedReservedFirstError = { messageId: 'listReservedPropsFirst', type: 'JSXIdentifier', }; const expectedEmptyReservedFirstError = { messageId: 'listIsEmpty', }; const expectedInvalidReservedFirstError = { messageId: 'noUnreservedProps', data: { unreservedWords: 'notReserved' }, }; const callbacksLastArgs = [{ callbacksLast: true }]; const ignoreCaseAndCallbackLastArgs = [ { callbacksLast: true, ignoreCase: true, }, ]; const reservedFirstAndCallbacksLastArgs = [ { callbacksLast: true, reservedFirst: true, }, ]; const shorthandFirstArgs = [{ shorthandFirst: true }]; const shorthandLastArgs = [{ shorthandLast: true }]; const shorthandAndCallbackLastArgs = [ { callbacksLast: true, shorthandLast: true, }, ]; const ignoreCaseArgs = [{ ignoreCase: true }]; const noSortAlphabeticallyArgs = [{ noSortAlphabetically: true }]; const sortAlphabeticallyArgs = [{ noSortAlphabetically: false }]; const reservedFirstAsBooleanArgs = [{ reservedFirst: true }]; const reservedFirstAsArrayArgs = [ { reservedFirst: ['children', 'dangerouslySetInnerHTML', 'key'] }, ]; const reservedFirstWithNoSortAlphabeticallyArgs = [ { noSortAlphabetically: true, reservedFirst: true, }, ]; const reservedFirstWithShorthandLast = [ { reservedFirst: true, shorthandLast: true, }, ]; const reservedFirstAsEmptyArrayArgs = [{ reservedFirst: [] }]; const reservedFirstAsInvalidArrayArgs = [{ reservedFirst: ['notReserved'] }]; const multilineFirstArgs = [{ multiline: 'first' }]; const multilineAndShorthandFirstArgs = [ { multiline: 'first', shorthandFirst: true, }, ]; const multilineLastArgs = [{ multiline: 'last' }]; const multilineAndShorthandAndCallbackLastArgs = [ { multiline: 'last', shorthandLast: true, callbacksLast: true, }, ]; ruleTester.run('jsx-sort-props', rule, { valid: parsers.all( [].concat( { code: '<App />;' }, { code: '<App {...this.props} />;' }, { code: '<App a b c />;' }, { code: '<App {...this.props} a b c />;' }, { code: '<App c {...this.props} a b />;' }, { code: '<App a="c" b="b" c="a" />;' }, { code: '<App {...this.props} a="c" b="b" c="a" />;' }, { code: '<App c="a" {...this.props} a="c" b="b" />;' }, { code: '<App A a />;' }, { code: '<App aB aa/>;' }, { code: '<App aA aB />;' }, { code: '<App aB aaa />;' }, { code: '<App a aB aa />;' }, { code: '<App Number="2" name="John" />;' }, // Ignoring case { code: '<App a A />;', options: ignoreCaseArgs }, { code: '<App aa aB />;', options: ignoreCaseArgs }, { code: '<App a B c />;', options: ignoreCaseArgs }, { code: '<App A b C />;', options: ignoreCaseArgs }, { code: '<App name="John" Number="2" />;', options: ignoreCaseArgs }, // Sorting callbacks below all other props { code: '<App a z onBar onFoo />;', options: callbacksLastArgs }, { code: '<App z onBar onFoo />;', options: ignoreCaseAndCallbackLastArgs, }, // Sorting shorthand props before others { code: '<App a b="b" />;', options: shorthandFirstArgs }, { code: '<App z a="a" />;', options: shorthandFirstArgs }, { code: '<App x y z a="a" b="b" />;', options: shorthandFirstArgs }, { code: '<App a="a" b="b" x y z />;', options: shorthandLastArgs }, { code: '<App a="a" b="b" x y z onBar onFoo />;', options: shorthandAndCallbackLastArgs, }, // Sorting multiline props before others { code: ` <App a={{ aA: 1, }} b /> `, options: multilineFirstArgs, }, { code: ` <App a={{ aA: 1, }} b={[ 1, ]} c d /> `, options: multilineFirstArgs, }, { code: ` <App a b c={{ cC: 1, }} d={[ 1, ]} e="1" /> `, options: multilineAndShorthandFirstArgs, }, // Sorting multiline props after others { code: ` <App a b={{ bB: 1, }} /> `, options: multilineLastArgs, }, { code: ` <App a b c="1" d={{ dD: 1, }} e={[ 1, ]} /> `, options: multilineLastArgs, }, { code: ` <App a={1} b="1" c={{ cC: 1, }} d={() => ( 1 )} e f onClick={() => ({ gG: 1, })} /> `, options: multilineAndShorthandAndCallbackLastArgs, }, // noSortAlphabetically { code: '<App a b />;', options: noSortAlphabeticallyArgs }, { code: '<App b a />;', options: noSortAlphabeticallyArgs }, // reservedFirst { code: '<App children={<App />} key={0} ref="r" a b c />', options: reservedFirstAsBooleanArgs, }, { code: '<App children={<App />} key={0} ref="r" a b c dangerouslySetInnerHTML={{__html: "EPR"}} />', options: reservedFirstAsBooleanArgs, }, { code: '<App children={<App />} key={0} a ref="r" />', options: reservedFirstAsArrayArgs, }, { code: '<App children={<App />} key={0} a dangerouslySetInnerHTML={{__html: "EPR"}} ref="r" />', options: reservedFirstAsArrayArgs, }, { code: '<App ref="r" key={0} children={<App />} b a c />', options: reservedFirstWithNoSortAlphabeticallyArgs, }, { code: '<div ref="r" dangerouslySetInnerHTML={{__html: "EPR"}} key={0} children={<App />} b a c />', options: reservedFirstWithNoSortAlphabeticallyArgs, }, { code: '<App key="key" c="c" b />', options: reservedFirstWithShorthandLast, }, { code: ` <RawFileField onChange={handleChange} onFileRemove={asMedia ? null : handleRemove} {...props} /> `, }, semver.satisfies(process.version, '>= 13') ? { code: ` <RawFileField onFileRemove={asMedia ? null : handleRemove} onChange={handleChange} {...props} /> `, options: [{ locale: 'sk-SK' }], } : [] ) ), invalid: parsers.all( [].concat( { code: '<App b a />;', errors: [expectedError], output: '<App a b />;', }, { code: '<App aB a />;', errors: [expectedError], output: '<App a aB />;', }, { code: '<App fistName="John" tel={5555555} name="John Smith" lastName="Smith" Number="2" />;', errors: [expectedError, expectedError, expectedError], output: '<App Number="2" fistName="John" lastName="Smith" name="John Smith" tel={5555555} />;', }, { code: '<App aa aB />;', errors: [expectedError], output: '<App aB aa />;', }, { code: '<App aB aA />;', errors: [expectedError], output: '<App aA aB />;', }, { code: '<App aaB aA />;', errors: [expectedError], output: '<App aA aaB />;', }, { code: '<App aaB aaa aA a />;', errors: [expectedError, expectedError], output: '<App a aA aaB aaa />;', }, { code: '<App {...this.props} b a />;', errors: [expectedError], output: '<App {...this.props} a b />;', }, { code: '<App c {...this.props} b a />;', errors: [expectedError], output: '<App c {...this.props} a b />;', }, { code: '<App fistName="John" tel={5555555} name="John Smith" lastName="Smith" Number="2" />;', options: ignoreCaseArgs, errors: [expectedError, expectedError, expectedError], output: '<App fistName="John" lastName="Smith" name="John Smith" Number="2" tel={5555555} />;', }, { code: '<App B a />;', options: ignoreCaseArgs, errors: [expectedError], output: '<App a B />;', }, { code: '<App B A c />;', options: ignoreCaseArgs, errors: [expectedError], output: '<App A B c />;', }, { code: '<App c="a" a="c" b="b" />;', output: '<App a="c" b="b" c="a" />;', errors: 2, }, { code: '<App {...this.props} c="a" a="c" b="b" />;', output: '<App {...this.props} a="c" b="b" c="a" />;', errors: 2, }, { code: '<App d="d" b="b" {...this.props} c="a" a="c" />;', output: '<App b="b" d="d" {...this.props} a="c" c="a" />;', errors: 2, }, { code: ` <App a={true} z r _onClick={function(){}} onHandle={function(){}} {...this.props} b={false} {...otherProps} > {test} </App> `, output: ` <App _onClick={function(){}} a={true} onHandle={function(){}} r z {...this.props} b={false} {...otherProps} > {test} </App> `, errors: 3, }, { code: '<App b={2} c={3} d={4} e={5} f={6} g={7} h={8} i={9} j={10} k={11} a={1} />', output: '<App a={1} b={2} c={3} d={4} e={5} f={6} g={7} h={8} i={9} j={10} k={11} />', errors: 1, }, { code: ` <List className={className} onStageAnswer={onStageAnswer} onCommitAnswer={onCommitAnswer} isFocused={isFocused} direction={direction} allowMultipleSelection={allowMultipleSelection} measureLongestChildNode={measureLongestChildNode} layoutItemsSize={layoutItemsSize} handleAppScroll={handleAppScroll} isActive={isActive} resetSelection={resetSelection} onKeyboardChoiceHovered={onKeyboardChoiceHovered} keyboardShortcutType /> `, output: ` <List allowMultipleSelection={allowMultipleSelection} className={className} direction={direction} handleAppScroll={handleAppScroll} isActive={isActive} isFocused={isFocused} keyboardShortcutType layoutItemsSize={layoutItemsSize} measureLongestChildNode={measureLongestChildNode} onCommitAnswer={onCommitAnswer} onKeyboardChoiceHovered={onKeyboardChoiceHovered} onStageAnswer={onStageAnswer} resetSelection={resetSelection} /> `, errors: 10, }, { code: ` <CreateNewJob closed={false} flagOptions={flagOptions} jobHeight={300} jobWidth={200} campaign='Some Campaign name' campaignStart={moment('2018-07-28 00:00:00')} campaignFinish={moment('2018-09-01 00:00:00')} jobNumber={'Job Number can be a String'} jobTemplateOptions={jobTemplateOptions} numberOfPages={30} onChange={onChange} onClose={onClose} spreadSheetTemplateOptions={spreadSheetTemplateOptions} stateMachineOptions={stateMachineOptions} workflowTemplateOptions={workflowTemplateOptions} workflowTemplateSteps={workflowTemplateSteps} description='Some description for this job' jobTemplate='1' stateMachine='1' flag='1' spreadSheetTemplate='1' workflowTemplate='1' validation={validation} onSubmit={onSubmit} /> `, output: ` <CreateNewJob campaign='Some Campaign name' campaignFinish={moment('2018-09-01 00:00:00')} campaignStart={moment('2018-07-28 00:00:00')} closed={false} description='Some description for this job' flag='1' flagOptions={flagOptions} jobHeight={300} jobNumber={'Job Number can be a String'} jobTemplate='1' jobTemplateOptions={jobTemplateOptions} jobWidth={200} numberOfPages={30} onChange={onChange} onClose={onClose} onSubmit={onSubmit} spreadSheetTemplate='1' spreadSheetTemplateOptions={spreadSheetTemplateOptions} stateMachine='1' stateMachineOptions={stateMachineOptions} validation={validation} workflowTemplate='1' workflowTemplateOptions={workflowTemplateOptions} workflowTemplateSteps={workflowTemplateSteps} /> `, errors: 13, }, { code: '<App key="key" b c="c" />', errors: [expectedShorthandLastError], options: reservedFirstWithShorthandLast, output: '<App key="key" c="c" b />', }, { code: '<App ref="ref" key="key" isShorthand veryLastAttribute="yes" />', errors: [expectedError, expectedShorthandLastError], options: reservedFirstWithShorthandLast, output: '<App key="key" ref="ref" veryLastAttribute="yes" isShorthand />', }, { code: '<App a z onFoo onBar />;', errors: [expectedError], options: callbacksLastArgs, output: '<App a z onBar onFoo />;', }, { code: '<App a onBar onFoo z />;', errors: [expectedCallbackError], options: callbacksLastArgs, output: '<App a z onBar onFoo />;', }, { code: '<App a="a" b />;', errors: [expectedShorthandFirstError], options: shorthandFirstArgs, output: '<App b a="a" />;', }, { code: '<App z x a="a" />;', errors: [expectedError], options: shorthandFirstArgs, output: '<App x z a="a" />;', }, { code: '<App b a="a" />;', errors: [expectedShorthandLastError], options: shorthandLastArgs, output: '<App a="a" b />;', }, { code: '<App a="a" onBar onFoo z x />;', errors: [expectedError], options: shorthandLastArgs, output: '<App a="a" onBar onFoo x z />;', }, { code: '<App b a />;', errors: [expectedError], options: sortAlphabeticallyArgs, output: '<App a b />;', }, // reservedFirst { code: '<App a key={1} />', options: reservedFirstAsBooleanArgs, errors: [expectedReservedFirstError], output: '<App key={1} a />', }, { code: '<div a dangerouslySetInnerHTML={{__html: "EPR"}} />', options: reservedFirstAsBooleanArgs, errors: [expectedReservedFirstError], output: '<div dangerouslySetInnerHTML={{__html: "EPR"}} a />', }, { code: '<App ref="r" key={2} b />', options: reservedFirstAsBooleanArgs, errors: [expectedError], output: '<App key={2} ref="r" b />', }, { code: '<App key={2} b a />', options: reservedFirstAsBooleanArgs, output: '<App key={2} a b />', errors: [expectedError], }, { code: '<App b a />', options: reservedFirstAsBooleanArgs, output: '<App a b />', errors: [expectedError], }, { code: '<App dangerouslySetInnerHTML={{__html: "EPR"}} e key={2} b />', options: reservedFirstAsBooleanArgs, output: '<App key={2} b dangerouslySetInnerHTML={{__html: "EPR"}} e />', errors: [expectedReservedFirstError, expectedError], }, { code: '<App key={3} children={<App />} />', options: reservedFirstAsArrayArgs, errors: [expectedError], output: '<App children={<App />} key={3} />', }, { code: '<App z ref="r" />', options: reservedFirstWithNoSortAlphabeticallyArgs, errors: [expectedReservedFirstError], output: '<App ref="r" z />', }, { code: '<App key={4} />', options: reservedFirstAsEmptyArrayArgs, errors: [expectedEmptyReservedFirstError], }, { code: '<App key={5} />', options: reservedFirstAsInvalidArrayArgs, errors: [expectedInvalidReservedFirstError], }, { code: '<App onBar z />;', output: '<App z onBar />;', options: reservedFirstAndCallbacksLastArgs, errors: [expectedCallbackError], // multiline first }, { code: ` <App a b={{ bB: 1, }} /> `, options: multilineFirstArgs, errors: [expectedMultilineFirstError], output: ` <App b={{ bB: 1, }} a /> `, }, { code: ` <App a={1} b={{ bB: 1, }} c /> `, options: multilineAndShorthandFirstArgs, errors: [expectedMultilineFirstError, expectedShorthandFirstError], output: ` <App c b={{ bB: 1, }} a={1} /> `, }, // multiline last { code: ` <App a={{ aA: 1, }} b /> `, options: multilineLastArgs, errors: [expectedMultilineLastError], output: ` <App b a={{ aA: 1, }} /> `, }, { code: ` <App a={{ aA: 1, }} b inline={1} onClick={() => ({ c: 1 })} d="dD" e={() => ({ eE: 1 })} f /> `, options: multilineAndShorthandAndCallbackLastArgs, errors: [ { messageId: 'listShorthandLast', line: 6, }, { messageId: 'listCallbacksLast', line: 8, }, ], output: ` <App d="dD" inline={1} a={{ aA: 1, }} e={() => ({ eE: 1 })} b f onClick={() => ({ c: 1 })} /> `, }, { code: ` <Typography float className={classNames(classes.inputWidth, { [classes.noBorder]: isActive === "values", })} disabled={isDisabled} initialValue={computePercentage(number, count)} InputProps={{ ...customInputProps, }} key={index} isRequired {...sharedTypographyProps} ref={textRef} min="0" name="fieldName" placeholder={getTranslation("field")} onValidate={validate} inputProps={{ className: inputClassName, }} outlined {...rest} /> `, options: [ { multiline: 'last', shorthandFirst: true, callbacksLast: true, reservedFirst: true, ignoreCase: true, }, ], output: ` <Typography key={index} float isRequired disabled={isDisabled} initialValue={computePercentage(number, count)} className={classNames(classes.inputWidth, { [classes.noBorder]: isActive === "values", })} InputProps={{ ...customInputProps, }} {...sharedTypographyProps} ref={textRef} outlined min="0" name="fieldName" placeholder={getTranslation("field")} inputProps={{ className: inputClassName, }} onValidate={validate} {...rest} /> `, errors: [ { messageId: 'listMultilineLast', line: 4, }, { messageId: 'listReservedPropsFirst', line: 12, }, { messageId: 'listShorthandFirst', line: 13, }, { messageId: 'listCallbacksLast', line: 19, }, ], }, semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <foo m={0} n={0} // this is n o={0} c={0} // this is c // fofof f={0} // this is f a={0} b={0} d={0} /> `, output: ` <foo a={0} b={0} d={0} m={0} n={0} // this is n o={0} c={0} // this is c // fofof f={0} // this is f /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 6, }, { messageId: 'sortPropsByAlpha', line: 8, }, { messageId: 'sortPropsByAlpha', line: 9, }, { messageId: 'sortPropsByAlpha', line: 10, }, { messageId: 'sortPropsByAlpha', line: 11, }, ], } : [], semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <foo m={0} n={0} // this is n o={0} c={0} // this is c f={0} // this is f e={0} a={0} b={0} d={0} /> `, output: ` <foo a={0} b={0} c={0} // this is c d={0} e={0} f={0} // this is f m={0} n={0} // this is n o={0} /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 6, }, { messageId: 'sortPropsByAlpha', line: 7, }, { messageId: 'sortPropsByAlpha', line: 8, }, { messageId: 'sortPropsByAlpha', line: 9, }, { messageId: 'sortPropsByAlpha', line: 10, }, { messageId: 'sortPropsByAlpha', line: 11, }, ], } : [], semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <foo a1={0} g={0} d={0} // comment for d // comment for d and aa aa={0} c={0} // comment for c // comment for c and e e={1} ab={1} // comment for ab f={0} /> `, output: ` <foo a1={0} ab={1} // comment for ab f={0} g={0} c={0} // comment for c // comment for c and e e={1} d={0} // comment for d // comment for d and aa aa={0} /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 5, }, { messageId: 'sortPropsByAlpha', line: 7, }, { messageId: 'sortPropsByAlpha', line: 8, }, { messageId: 'sortPropsByAlpha', line: 10, }, { messageId: 'sortPropsByAlpha', line: 11, }, { messageId: 'sortPropsByAlpha', line: 12, }, ], } : [], semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <foo a1={0} ab={1} // comment for ab and f f={0} g={0} c={0} // comment for c // comment for c and e e={1} d={0} aa={1} // comment for aa /> `, output: ` <foo a1={0} aa={1} // comment for aa d={0} g={0} ab={1} // comment for ab and f f={0} c={0} // comment for c // comment for c and e e={1} /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 8, }, { messageId: 'sortPropsByAlpha', line: 10, }, { messageId: 'sortPropsByAlpha', line: 11, }, { messageId: 'sortPropsByAlpha', line: 12, }, ], } : [], semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <foo a={0} b={1} /* comment for b and ab */ ab={1} aa={0} /> `, output: ` <foo a={0} aa={0} b={1} /* comment for b and ab */ ab={1} /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 2, }, { messageId: 'sortPropsByAlpha', line: 2, }, ], } : [], semver.satisfies(eslintPkg.version, '> 3') ? { code: ` <ReblendJson src={rowResult} name="data" collapsed={4} collapseStringsAfterLength={60} onEdit={onEdit} /* onDelete={onEdit} */ /> `, output: ` <ReblendJson collapseStringsAfterLength={60} collapsed={4} name="data" src={rowResult} onEdit={onEdit} /* onDelete={onEdit} */ /> `, errors: [ { messageId: 'sortPropsByAlpha', line: 2, }, { messageId: 'sortPropsByAlpha', line: 2, }, { messageId: 'sortPropsByAlpha', line: 2, }, { messageId: 'sortPropsByAlpha', line: 2, }, ], } : [], { code: ` <Page // Pass all the props to the Page component. {...props} // Use the platform specific props from the doc.ts file. {...TemplatePageProps[platform]} // Use the getSubTitle helper function to get the page header subtitle from the active platform. subTitle={getSubTitle(platform)} // You can define custom sections using the \`otherSections\` prop. // Here it is using a method that takes the platform as an argument to return the correct array of section props. otherSections={_otherSections(platform) as IPageSectionProps[]} // You can hide the side rail by setting \`showSideRail\` to false. // showSideRail={false} // You can pass a custom className to the page wrapper if needed. // className="customPageClassName" /> `, features: ['ts', 'no-babel-old'], errors: [ { messageId: 'sortPropsByAlpha', line: 11, }, ], } ) ), });