diff --git a/README.md b/README.md index 1a67b37..0af027e 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Where rules are included in the configs `recommended`, `slim`, `all` or `depreca * [`no-jquery/no-data`](docs/rules/no-data.md) `all` * [`no-jquery/no-deferred`](docs/rules/no-deferred.md) `all` * [`no-jquery/no-delegate`](docs/rules/no-delegate.md) `3.0`, `all` +* [`no-jquery/no-done-fail`](docs/rules/no-done-fail.md) * [`no-jquery/no-each`](docs/rules/no-each.md) * [`no-jquery/no-each-collection`](docs/rules/no-each-collection.md) `all` * [`no-jquery/no-each-util`](docs/rules/no-each-util.md) `all` diff --git a/docs/rules/no-done-fail.md b/docs/rules/no-done-fail.md new file mode 100644 index 0000000..10ec357 --- /dev/null +++ b/docs/rules/no-done-fail.md @@ -0,0 +1,25 @@ +[//]: # (This file is generated by eslint-docgen. Do not edit it directly.) + +# no-done-fail + +Disallows the [`.done`](https://api.jquery.com/deferred.done/)/[`.fail`](https://api.jquery.com/deferred.fail/) methods. Prefer `.then`. + +## Rule details + +❌ Examples of **incorrect** code: +```js +promise.done( callback ); +promise.fail( callback ); +``` + +✔️ Examples of **correct** code: +```js +done(); +fail(); +promise.then( doneCallback, failCallback ); +``` + +## Resources + +* [Rule source](/src/rules/no-done-fail.js) +* [Test source](/tests/rules/no-done-fail.js) diff --git a/src/index.js b/src/index.js index 63080d7..e8b0f1e 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ module.exports = { 'no-deferred': require( './rules/no-deferred' ), 'no-delegate': require( './rules/no-delegate' ), 'no-die': require( './rules/no-die' ), + 'no-done-fail': require( './rules/no-done-fail' ), 'no-each': require( './rules/no-each' ), 'no-each-collection': require( './rules/no-each-collection' ), 'no-each-util': require( './rules/no-each-util' ), @@ -333,8 +334,10 @@ module.exports = { 'no-jquery/no-filter': 'warn', 'no-jquery/no-prop': 'warn', 'no-jquery/no-sub': 'warn', - 'no-jquery/no-text': 'warn' + 'no-jquery/no-text': 'warn', + // Other methods + 'no-jquery/no-done-fail': 'warn' } } } diff --git a/src/rules/no-done-fail.js b/src/rules/no-done-fail.js new file mode 100644 index 0000000..d562efc --- /dev/null +++ b/src/rules/no-done-fail.js @@ -0,0 +1,11 @@ +'use strict'; + +const utils = require( '../utils.js' ); + +module.exports = utils.createUniversalMethodRule( + [ 'done', 'fail' ], + ( node ) => node === true ? + 'Prefer `.then`' : + `Prefer .then to .${ node.callee.property.name }`, + ( method ) => `[\`.${ method }\`](https://api.jquery.com/deferred.${ method }/)` +); diff --git a/src/utils.js b/src/utils.js index e9208bc..c4a2353 100644 --- a/src/utils.js +++ b/src/utils.js @@ -527,6 +527,47 @@ function createCollectionOrUtilMethodRule( methods, message, options ) { } ), description, options.fixable, options.deprecated ); } +/** + * Create a rule for a method on any object + * + * @param {string|string[]} methods Method or list of method names + * @param {string|Function} message Message to report. See createCollectionMethodRule. + * @param {Function} linkGenerator Function to generate a markdown link + * @param {Object} [options] Options. See createCollectionMethodRule. + * for a given function name. + * @return {Object} Rule + */ +function createUniversalMethodRule( methods, message, linkGenerator, options ) { + options = options || {}; + + options.mode = 'util'; + + methods = Array.isArray( methods ) ? methods : [ methods ]; + + let description = 'Disallows the ' + methods.map( linkGenerator ).join( '/' ) + ' ' + + ( methods.length > 1 ? 'methods' : 'method' ) + '.'; + + description += messageSuffix( message ); + + return createRule( ( context ) => ( { + 'CallExpression:exit': ( node ) => { + if ( node.callee.type !== 'MemberExpression' ) { + return; + } + const name = node.callee.property.name; + if ( !methods.includes( name ) ) { + return; + } + + context.report( { + node, + message: messageToPlainString( message, node, name, options ), + fix: options.fix && options.fix.bind( this, node, context ) + } ); + } + } ), description, options.fixable, options.deprecated ); +} + function eventShorthandFixer( node, context, fixer ) { const name = node.callee.property.name; if ( node.callee.parent.arguments.length ) { @@ -580,6 +621,7 @@ module.exports = { createUtilMethodRule, createUtilPropertyRule, createCollectionOrUtilMethodRule, + createUniversalMethodRule, eventShorthandFixer, jQueryCollectionLink, jQueryGlobalLink, diff --git a/test-self/all/methods.js b/test-self/all/methods.js index 42f4107..ac345b7 100644 --- a/test-self/all/methods.js +++ b/test-self/all/methods.js @@ -239,3 +239,9 @@ $x.wrap(); $x.wrapAll(); // eslint-disable-next-line self/no-wrap $x.wrapInner(); + +// Other methods +// eslint-disable-next-line self/no-done-fail +promise.done(); +// eslint-disable-next-line self/no-done-fail +promise.fail(); diff --git a/tests/rules/no-done-fail.js b/tests/rules/no-done-fail.js new file mode 100644 index 0000000..ea66198 --- /dev/null +++ b/tests/rules/no-done-fail.js @@ -0,0 +1,21 @@ +'use strict'; + +const rule = require( '../../src/rules/no-done-fail' ); +const RuleTester = require( '../../tools/rule-tester' ); + +const error = ( method ) => `Prefer .then to .${ method }`; + +const ruleTester = new RuleTester(); +ruleTester.run( 'no-done-fail', rule, { + valid: [ 'promise.then( doneCallback, failCallback )', 'done()', 'fail()' ], + invalid: [ + { + code: 'promise.done( callback )', + errors: [ error( 'done' ) ] + }, + { + code: 'promise.fail( callback )', + errors: [ error( 'fail' ) ] + } + ] +} );