From 9d9786af8468293824c5752cf6da5d082274074d Mon Sep 17 00:00:00 2001 From: Ed Sanders Date: Wed, 8 Oct 2025 19:09:05 +0100 Subject: [PATCH] New rule: `no-toggle-all-classes`; included in deprecated-3.0 $x.toggleClass(false) which turns off all classes was deprecated in 3.0, and will be removed in 4. Other signatures are unaffected, so make this a separate rule to `no-class`. --- README.md | 1 + docs/rules/no-toggle-all-classes.md | 34 +++++++++++++++++++ src/index.js | 2 ++ src/rules/no-toggle-all-classes.js | 50 ++++++++++++++++++++++++++++ test-self/deprecated-3.0/test.js | 3 ++ tests/rules/no-toggle-all-classes.js | 38 +++++++++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 docs/rules/no-toggle-all-classes.md create mode 100644 src/rules/no-toggle-all-classes.js create mode 100644 tests/rules/no-toggle-all-classes.js diff --git a/README.md b/README.md index 7d6c9bed..49398038 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ Where rules are included in the configs `recommended`, `slim`, `all` or `depreca * [`no-jquery/no-sub`](docs/rules/no-sub.md) `1.7`, `all` * [`no-jquery/no-support`](docs/rules/no-support.md) `1.9` * [`no-jquery/no-text`](docs/rules/no-text.md) `all` +* [`no-jquery/no-toggle-all-classes`](docs/rules/no-toggle-all-classes.md) `3.0` * [`no-jquery/no-trigger`](docs/rules/no-trigger.md) `all` * [`no-jquery/no-trim`](docs/rules/no-trim.md) `3.5`, `all` * [`no-jquery/no-type`](docs/rules/no-type.md) `3.3`, `all` diff --git a/docs/rules/no-toggle-all-classes.md b/docs/rules/no-toggle-all-classes.md new file mode 100644 index 00000000..611deeb1 --- /dev/null +++ b/docs/rules/no-toggle-all-classes.md @@ -0,0 +1,34 @@ +[//]: # (This file is generated by eslint-docgen. Do not edit it directly.) + +# no-toggle-all-classes + +Disallows using [`.toggleClass`](https://api.jquery.com/toggleClass/) to toggle all classes. + +📋 This rule is enabled in `plugin:no-jquery/deprecated-3.0`. + +## Rule details + +❌ Examples of **incorrect** code: +```js +$div.toggleClass(); +$div.toggleClass( false ); +$div.toggleClass( true ); +$div.toggleClass( undefined ); +``` + +✔️ Examples of **correct** code: +```js +$div.attr( 'class', '' ); +$div.removeClass( 'myClass' ); +toggleClass( false ); +obj.toggleClass( false ); +$div.toggleClass( 'myClass', true ); +$div.toggleClass( 'myClass', false ); +$div.toggleClass( 'myClass' ); +$div.toggleClass( 'myClass', undefined ); +``` + +## Resources + +* [Rule source](/src/rules/no-toggle-all-classes.js) +* [Test source](/tests/rules/no-toggle-all-classes.js) diff --git a/src/index.js b/src/index.js index c5d492e8..162e426d 100644 --- a/src/index.js +++ b/src/index.js @@ -92,6 +92,7 @@ module.exports = { 'no-support': require( './rules/no-support' ), 'no-text': require( './rules/no-text' ), 'no-toggle': require( './rules/no-toggle' ), + 'no-toggle-all-classes': require( './rules/no-toggle-all-classes' ), 'no-trigger': require( './rules/no-trigger' ), 'no-trim': require( './rules/no-trim' ), 'no-type': require( './rules/no-type' ), @@ -184,6 +185,7 @@ module.exports = { 'no-jquery/no-fx-interval': 'warn', 'no-jquery/no-parse-json': 'warn', 'no-jquery/no-ready-shorthand': 'warn', + 'no-jquery/no-toggle-all-classes': 'warn', 'no-jquery/no-unique': 'warn' } }, diff --git a/src/rules/no-toggle-all-classes.js b/src/rules/no-toggle-all-classes.js new file mode 100644 index 00000000..27fcf49d --- /dev/null +++ b/src/rules/no-toggle-all-classes.js @@ -0,0 +1,50 @@ +'use strict'; + +const utils = require( '../utils.js' ); + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallows using ' + utils.jQueryCollectionLink( 'toggleClass' ) + ' to toggle all classes.' + }, + schema: [] + }, + + create: ( context ) => ( { + 'CallExpression:exit': ( node ) => { + if ( node.callee.type !== 'MemberExpression' ) { + return; + } + const name = node.callee.property.name; + if ( name !== 'toggleClass' || utils.isjQueryConstructor( context, node.callee.object.name ) ) { + return; + } + + if ( node.arguments.length >= 2 ) { + return; + } + if ( node.arguments.length === 1 ) { + const arg = node.arguments[ 0 ]; + if ( + !( + arg.type === 'Literal' && + ( arg.value === true || arg.value === false ) + ) && + !( + arg.type === 'Identifier' && arg.name === 'undefined' + ) + ) { + return; + } + } + + if ( utils.isjQuery( context, node.callee ) ) { + context.report( { + node, + message: '.toggleClass(boolean|undefined) is deprecated' + } ); + } + } + } ) +}; diff --git a/test-self/deprecated-3.0/test.js b/test-self/deprecated-3.0/test.js index e776be49..a9e1a43f 100644 --- a/test-self/deprecated-3.0/test.js +++ b/test-self/deprecated-3.0/test.js @@ -29,6 +29,9 @@ $x.ready(); // eslint-disable-next-line self/no-unique $.unique(); +// eslint-disable-next-line self/no-toggle-all-classes +$x.toggleClass( false ); + /* 1.10 */ // eslint-disable-next-line self/no-context-prop $x.context; diff --git a/tests/rules/no-toggle-all-classes.js b/tests/rules/no-toggle-all-classes.js new file mode 100644 index 00000000..d9fd3c65 --- /dev/null +++ b/tests/rules/no-toggle-all-classes.js @@ -0,0 +1,38 @@ +'use strict'; + +const rule = require( '../../src/rules/no-toggle-all-classes' ); +const RuleTester = require( '../../tools/rule-tester' ); + +const toggleError = '.toggleClass(boolean|undefined) is deprecated'; + +const ruleTester = new RuleTester(); +ruleTester.run( 'no-toggle-all-classes', rule, { + valid: [ + '$div.attr("class", "")', + '$div.removeClass("myClass")', + 'toggleClass(false)', + 'obj.toggleClass(false)', + '$div.toggleClass("myClass", true)', + '$div.toggleClass("myClass", false)', + '$div.toggleClass("myClass")', + '$div.toggleClass("myClass", undefined)' + ], + invalid: [ + { + code: '$div.toggleClass()', + errors: [ toggleError ] + }, + { + code: '$div.toggleClass(false)', + errors: [ toggleError ] + }, + { + code: '$div.toggleClass(true)', + errors: [ toggleError ] + }, + { + code: '$div.toggleClass(undefined)', + errors: [ toggleError ] + } + ] +} );