eslint-plugin-unicorn 61.0.0 → 61.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "61.0.0",
3
+ "version": "61.0.1",
4
4
  "description": "More than 100 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -78,6 +78,40 @@ const getConditionText = (node, sourceCode, isNegative) => {
78
78
  return text;
79
79
  };
80
80
 
81
+ const isClassListMethodCall = (node, methods) =>
82
+ isMethodCall(node, {
83
+ methods,
84
+ argumentsLength: 1,
85
+ optionalCall: false,
86
+ optionalMember: false,
87
+ })
88
+ && isClassList(node.callee.object);
89
+
90
+ const isSameElementAndClassName = (callExpressionA, callExpressionB) =>
91
+ isSameReference(callExpressionA.callee.object, callExpressionB.callee.object)
92
+ && isSameReference(callExpressionA.arguments[0], callExpressionB.arguments[0]);
93
+
94
+ const getClassListContainsCall = (conditionNode, isNegative, addOrRemoveCall) => {
95
+ if (!isNegative) {
96
+ if (!(conditionNode.type === 'UnaryExpression' && conditionNode.operator === '!' && conditionNode.prefix)) {
97
+ return;
98
+ }
99
+
100
+ return getClassListContainsCall(conditionNode.argument, !isNegative, addOrRemoveCall);
101
+ }
102
+
103
+ if (conditionNode.type === 'ChainExpression') {
104
+ conditionNode = conditionNode.expression;
105
+ }
106
+
107
+ if (
108
+ isClassListMethodCall(conditionNode, ['contains'])
109
+ && isSameElementAndClassName(conditionNode, addOrRemoveCall)
110
+ ) {
111
+ return conditionNode;
112
+ }
113
+ };
114
+
81
115
  /** @param {import('eslint').Rule.RuleContext} context */
82
116
  const create = context => {
83
117
  const {sourceCode} = context;
@@ -121,37 +155,30 @@ const create = context => {
121
155
 
122
156
  // `element.classList.add('className');`
123
157
  // `element.classList.remove('className');`
124
- if (!clauses.every(node =>
125
- isMethodCall(node, {
126
- methods: ['add', 'remove'],
127
- argumentsLength: 1,
128
- optionalCall: false,
129
- optionalMember: false,
130
- })
131
- && isClassList(node.callee.object),
132
- )) {
158
+ if (!clauses.every(node => isClassListMethodCall(node, ['add', 'remove']))) {
133
159
  return;
134
160
  }
135
161
 
136
162
  const [consequent, alternate] = clauses;
137
163
  if (
138
164
  (consequent.callee.property.name === alternate.callee.property.name)
139
- || !isSameReference(consequent.callee.object, alternate.callee.object)
140
- || !isSameReference(consequent.arguments[0], alternate.arguments[0])
165
+ || !isSameElementAndClassName(consequent, alternate)
141
166
  ) {
142
167
  return;
143
168
  }
144
169
 
145
170
  /** @param {import('eslint').Rule.RuleFixer} fixer */
146
171
  function * fix(fixer) {
147
- const isOptional = consequent.callee.object.optional || alternate.callee.object.optional;
148
172
  const elementText = getParenthesizedText(consequent.callee.object.object, sourceCode);
149
173
  const classNameText = getParenthesizedText(consequent.arguments[0], sourceCode);
150
174
  const isExpression = node.type === 'ConditionalExpression';
151
175
  const isNegative = consequent.callee.property.name === 'remove';
152
- const conditionText = getConditionText(node.test, sourceCode, isNegative);
176
+ const conditionNode = node.test;
177
+ const classListContainsCall = getClassListContainsCall(conditionNode, isNegative, consequent);
178
+ const conditionText = classListContainsCall ? '' : getConditionText(conditionNode, sourceCode, isNegative);
179
+ const isOptional = consequent.callee.object.optional || alternate.callee.object.optional || classListContainsCall?.callee.object.optional;
153
180
 
154
- let text = `${elementText}${isOptional ? '?' : ''}.classList.toggle(${classNameText}, ${conditionText})`;
181
+ let text = `${elementText}${isOptional ? '?' : ''}.classList.toggle(${classNameText}${conditionText ? `, ${conditionText}` : ''})`;
155
182
 
156
183
  if (!isExpression) {
157
184
  text = `${text};`;
@@ -195,9 +222,14 @@ const create = context => {
195
222
  /** @param {import('eslint').Rule.RuleFixer} fixer */
196
223
  function * fix(fixer) {
197
224
  const isNegative = conditionalExpression.consequent.value === 'remove';
198
- const conditionText = getConditionText(conditionalExpression.test, sourceCode, isNegative);
225
+ const conditionNode = conditionalExpression.test;
226
+ const classListContainsCall = getClassListContainsCall(conditionNode, isNegative, callExpression);
227
+ const conditionText = classListContainsCall ? '' : getConditionText(conditionNode, sourceCode, isNegative);
228
+
229
+ if (conditionText) {
230
+ yield fixer.insertTextAfter(callExpression.arguments[0], `, ${conditionText}`);
231
+ }
199
232
 
200
- yield fixer.insertTextAfter(callExpression.arguments[0], `, ${conditionText}`);
201
233
  yield replaceMemberExpressionProperty(fixer, classListMethod, sourceCode, '.toggle');
202
234
  }
203
235