eslint-plugin-unicorn 67.0.0 → 68.0.0
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/index.js +4 -0
- package/package.json +6 -3
- package/readme.md +153 -16
- package/rules/ast/is-empty-node.js +1 -5
- package/rules/better-dom-traversing.js +17 -8
- package/rules/class-reference-in-static-methods.js +14 -2
- package/rules/consistent-assert.js +1 -1
- package/rules/consistent-boolean-name.js +15 -3
- package/rules/consistent-compound-words.js +7 -5
- package/rules/consistent-conditional-object-spread.js +354 -0
- package/rules/consistent-destructuring.js +5 -0
- package/rules/consistent-existence-index-check.js +1 -1
- package/rules/consistent-json-file-read.js +4 -16
- package/rules/consistent-optional-chaining.js +3 -20
- package/rules/default-export-style.js +569 -0
- package/rules/expiring-todo-comments.js +1 -1
- package/rules/explicit-length-check.js +7 -32
- package/rules/import-style.js +8 -6
- package/rules/index.js +38 -1
- package/rules/logical-assignment-operators.js +17 -4
- package/rules/{prevent-abbreviations.js → name-replacements.js} +11 -9
- package/rules/new-for-builtins.js +39 -5
- package/rules/no-accessor-recursion.js +7 -0
- package/rules/no-accidental-bitwise-operator.js +97 -0
- package/rules/no-array-callback-reference.js +1 -7
- package/rules/no-array-concat-in-loop.js +45 -0
- package/rules/no-array-front-mutation.js +64 -0
- package/rules/no-array-sort-for-min-max.js +225 -0
- package/rules/no-array-splice.js +78 -64
- package/rules/no-boolean-sort-comparator.js +215 -0
- package/rules/no-break-in-nested-loop.js +33 -0
- package/rules/no-chained-comparison.js +116 -0
- package/rules/no-collection-bracket-access.js +165 -0
- package/rules/no-confusing-array-splice.js +2 -21
- package/rules/no-confusing-array-with.js +2 -20
- package/rules/no-constant-zero-expression.js +91 -0
- package/rules/no-declarations-before-early-exit.js +29 -36
- package/rules/no-double-comparison.js +117 -0
- package/rules/no-duplicate-if-branches.js +143 -0
- package/rules/no-duplicate-logical-operands.js +151 -0
- package/rules/no-for-each.js +11 -12
- package/rules/no-for-loop.js +3 -15
- package/rules/no-impossible-length-comparison.js +166 -0
- package/rules/no-incorrect-query-selector.js +8 -4
- package/rules/no-incorrect-template-string-interpolation.js +22 -11
- package/rules/no-instanceof-builtins.js +14 -1
- package/rules/no-invalid-argument-count.js +110 -54
- package/rules/no-invalid-character-comparison.js +121 -0
- package/rules/no-invalid-fetch-options.js +3 -1
- package/rules/no-keyword-prefix.js +14 -4
- package/rules/no-late-current-target-access.js +7 -39
- package/rules/no-loop-iterable-mutation.js +541 -0
- package/rules/no-manually-wrapped-comments.js +10 -2
- package/rules/no-misrefactored-assignment.js +102 -0
- package/rules/no-named-default.js +35 -2
- package/rules/no-nonstandard-builtin-properties.js +1381 -0
- package/rules/no-process-exit.js +16 -12
- package/rules/no-redundant-comparison.js +5 -12
- package/rules/no-selector-as-dom-name.js +150 -0
- package/rules/no-thenable.js +27 -11
- package/rules/no-uncalled-method.js +2 -8
- package/rules/no-undeclared-class-members.js +8 -6
- package/rules/no-unnecessary-boolean-comparison.js +131 -0
- package/rules/no-unnecessary-splice.js +15 -20
- package/rules/no-unreadable-for-of-expression.js +2 -1
- package/rules/no-unsafe-property-key.js +10 -15
- package/rules/no-unused-properties.js +4 -3
- package/rules/no-useless-coercion.js +71 -15
- package/rules/no-useless-compound-assignment.js +110 -0
- package/rules/no-useless-delete-check.js +353 -0
- package/rules/no-useless-else.js +16 -65
- package/rules/no-useless-error-capture-stack-trace.js +1 -1
- package/rules/no-useless-fallback-in-spread.js +7 -118
- package/rules/no-useless-logical-operand.js +248 -0
- package/rules/no-useless-undefined.js +11 -28
- package/rules/no-xor-as-exponentiation.js +59 -0
- package/rules/prefer-array-find.js +95 -5
- package/rules/prefer-array-flat-map.js +9 -21
- package/rules/prefer-array-flat.js +1 -24
- package/rules/prefer-array-from-async.js +273 -0
- package/rules/prefer-array-iterable-methods.js +157 -0
- package/rules/prefer-array-some.js +56 -31
- package/rules/prefer-at.js +1 -24
- package/rules/prefer-await.js +29 -0
- package/rules/prefer-boolean-return.js +224 -0
- package/rules/prefer-continue.js +361 -0
- package/rules/prefer-default-parameters.js +10 -6
- package/rules/prefer-dispose.js +1 -5
- package/rules/prefer-early-return.js +160 -30
- package/rules/prefer-else-if.js +50 -53
- package/rules/prefer-event-target.js +3 -13
- package/rules/prefer-flat-math-min-max.js +103 -0
- package/rules/prefer-global-this.js +1 -1
- package/rules/prefer-hoisting-branch-code.js +422 -0
- package/rules/prefer-identifier-import-export-specifiers.js +6 -11
- package/rules/prefer-includes-over-repeated-comparisons.js +5 -1
- package/rules/prefer-includes.js +6 -0
- package/rules/prefer-logical-operator-over-ternary.js +12 -20
- package/rules/prefer-math-constants.js +103 -0
- package/rules/prefer-math-min-max.js +1 -5
- package/rules/prefer-minimal-ternary.js +26 -2
- package/rules/prefer-node-protocol.js +1 -0
- package/rules/prefer-number-properties.js +2 -5
- package/rules/prefer-object-from-entries.js +6 -1
- package/rules/prefer-object-iterable-methods.js +36 -11
- package/rules/prefer-optional-catch-binding.js +30 -24
- package/rules/prefer-private-class-fields.js +2 -8
- package/rules/prefer-promise-with-resolvers.js +248 -0
- package/rules/prefer-query-selector.js +79 -17
- package/rules/prefer-queue-microtask.js +7 -9
- package/rules/prefer-regexp-escape.js +263 -0
- package/rules/prefer-set-size.js +10 -2
- package/rules/prefer-short-arrow-method.js +3 -3
- package/rules/prefer-single-replace.js +218 -0
- package/rules/prefer-spread.js +14 -21
- package/rules/prefer-string-replace-all.js +5 -0
- package/rules/prefer-structured-clone.js +2 -2
- package/rules/prefer-switch.js +1 -0
- package/rules/prefer-ternary.js +4 -2
- package/rules/prefer-type-literal-last.js +22 -54
- package/rules/prefer-unary-minus.js +103 -0
- package/rules/prefer-url-can-parse.js +308 -0
- package/rules/prefer-while-loop-condition.js +252 -0
- package/rules/require-passive-events.js +11 -4
- package/rules/require-proxy-trap-boolean-return.js +40 -126
- package/rules/rule/to-eslint-listener.js +5 -3
- package/rules/shared/array-concat-in-loop.js +151 -0
- package/rules/shared/identifier-checks.js +4 -16
- package/rules/shared/{abbreviations.js → name-replacements.js} +57 -0
- package/rules/shared/no-array-mutate-rule.js +19 -2
- package/rules/shared/regexp-escape.js +81 -0
- package/rules/shared/splice-replacements.js +1 -18
- package/rules/string-content.js +11 -4
- package/rules/template-indent.js +2 -8
- package/rules/throw-new-error.js +7 -0
- package/rules/utils/block-scope.js +42 -0
- package/rules/utils/builtins.js +50 -1
- package/rules/utils/comments.js +16 -0
- package/rules/utils/comparison.js +17 -0
- package/rules/utils/contains-suspension-point.js +35 -0
- package/rules/utils/get-const-variable-initializer.js +31 -0
- package/rules/utils/global-reference-tracker.js +14 -1
- package/rules/utils/has-optional-chain-element.js +36 -4
- package/rules/utils/index.js +32 -2
- package/rules/utils/is-boolean.js +23 -0
- package/rules/utils/is-function-self-used-inside.js +1 -5
- package/rules/utils/is-weak-map.js +21 -0
- package/rules/utils/is-weak-set.js +21 -0
- package/rules/utils/length-or-size.js +53 -0
- package/rules/utils/member-expression.js +18 -0
- package/rules/utils/needs-semicolon.js +1 -5
- package/rules/utils/numeric.js +20 -0
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +8 -14
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -5
- package/rules/utils/should-add-parentheses-to-unary-expression.js +8 -2
- package/rules/utils/track-branch-exits.js +79 -0
- package/rules/utils/types.js +22 -0
|
@@ -198,6 +198,16 @@ const isBooleanVariable = (variable, context) => {
|
|
|
198
198
|
return false;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// Destructuring patterns (`const {completed} = task`, `const [enabled] = list`) are intentionally not checked, since renaming a destructured binding is more involved than renaming a plain identifier.
|
|
202
|
+
if (definition.type === 'Variable' && definition.node.id.type !== 'Identifier') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// A setter parameter's name is positional and dictated by the accessor, not chosen freely.
|
|
207
|
+
if (definition.type === 'Parameter' && definition.node.parent?.kind === 'set') {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
201
211
|
const scope = sourceCode.getScope(name);
|
|
202
212
|
|
|
203
213
|
if (name.typeAnnotation) {
|
|
@@ -384,9 +394,11 @@ const create = context => {
|
|
|
384
394
|
}
|
|
385
395
|
|
|
386
396
|
const checkVariable = variable => {
|
|
397
|
+
// `hasBooleanPrefix` is a cheap string check, so run it before the expensive
|
|
398
|
+
// `isBooleanVariable` analysis.
|
|
387
399
|
if (
|
|
388
|
-
|
|
389
|
-
||
|
|
400
|
+
hasBooleanPrefix(variable.name, prefixes)
|
|
401
|
+
|| !isBooleanVariable(variable, context)
|
|
390
402
|
) {
|
|
391
403
|
return;
|
|
392
404
|
}
|
|
@@ -423,8 +435,8 @@ const create = context => {
|
|
|
423
435
|
|
|
424
436
|
if (
|
|
425
437
|
!name
|
|
426
|
-
|| !isBooleanProperty(node, context)
|
|
427
438
|
|| hasBooleanPrefix(name, prefixes)
|
|
439
|
+
|| !isBooleanProperty(node, context)
|
|
428
440
|
) {
|
|
429
441
|
return;
|
|
430
442
|
}
|
|
@@ -218,6 +218,13 @@ const create = context => {
|
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// The combined-regex gate is cheap and rejects most names, so run it before the
|
|
222
|
+
// import/shorthand checks.
|
|
223
|
+
const replacement = getNameReplacement(variable.name, options);
|
|
224
|
+
if (!replacement) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
221
228
|
const [definition] = variable.defs;
|
|
222
229
|
|
|
223
230
|
if (!shouldCheckDefaultOrNamespaceImportName(definition, options)) {
|
|
@@ -235,11 +242,6 @@ const create = context => {
|
|
|
235
242
|
return;
|
|
236
243
|
}
|
|
237
244
|
|
|
238
|
-
const replacement = getNameReplacement(variable.name, options);
|
|
239
|
-
if (!replacement) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
245
|
const scopes = [
|
|
244
246
|
...variable.references.map(reference => reference.from),
|
|
245
247
|
variable.scope,
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isEmptyObjectExpression,
|
|
3
|
+
isNullLiteral,
|
|
4
|
+
isUndefined,
|
|
5
|
+
} from './ast/index.js';
|
|
6
|
+
import {
|
|
7
|
+
getParenthesizedText,
|
|
8
|
+
isParenthesized,
|
|
9
|
+
isSameReference,
|
|
10
|
+
shouldAddParenthesesToConditionalExpressionChild,
|
|
11
|
+
shouldAddParenthesesToLogicalExpressionChild,
|
|
12
|
+
shouldAddParenthesesToUnaryExpressionArgument,
|
|
13
|
+
} from './utils/index.js';
|
|
14
|
+
|
|
15
|
+
const STYLE_LOGICAL = 'logical';
|
|
16
|
+
const STYLE_TERNARY = 'ternary';
|
|
17
|
+
|
|
18
|
+
const MESSAGE_ID = 'consistent-conditional-object-spread';
|
|
19
|
+
const messages = {
|
|
20
|
+
[MESSAGE_ID]: 'Prefer {{expectedStyle}} conditional object spreads.',
|
|
21
|
+
};
|
|
22
|
+
const nullishOperators = new Set(['==', '===']);
|
|
23
|
+
const nonNullishOperators = new Set(['!=', '!==']);
|
|
24
|
+
|
|
25
|
+
const isObjectSpreadArgument = node => (
|
|
26
|
+
node.parent.type === 'SpreadElement'
|
|
27
|
+
&& node.parent.argument === node
|
|
28
|
+
&& node.parent.parent.type === 'ObjectExpression'
|
|
29
|
+
&& node.parent.parent.properties.includes(node.parent)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function isSameNode(left, right, sourceCode) {
|
|
33
|
+
if (isSameReference(left, right)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (left.type !== right.type) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
switch (left.type) {
|
|
42
|
+
case 'AwaitExpression': {
|
|
43
|
+
return isSameNode(left.argument, right.argument, sourceCode);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case 'LogicalExpression': {
|
|
47
|
+
return (
|
|
48
|
+
left.operator === right.operator
|
|
49
|
+
&& isSameNode(left.left, right.left, sourceCode)
|
|
50
|
+
&& isSameNode(left.right, right.right, sourceCode)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case 'UnaryExpression': {
|
|
55
|
+
return (
|
|
56
|
+
left.operator === right.operator
|
|
57
|
+
&& left.prefix === right.prefix
|
|
58
|
+
&& isSameNode(left.argument, right.argument, sourceCode)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case 'UpdateExpression': {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// No default
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return sourceCode.getText(left) === sourceCode.getText(right);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getNullishKind(node) {
|
|
73
|
+
if (isNullLiteral(node)) {
|
|
74
|
+
return 'null';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isUndefined(node)) {
|
|
78
|
+
return 'undefined';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getNullishBinaryCheck(node) {
|
|
83
|
+
if (
|
|
84
|
+
node.type !== 'BinaryExpression'
|
|
85
|
+
|| (!nullishOperators.has(node.operator) && !nonNullishOperators.has(node.operator))
|
|
86
|
+
) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const leftKind = getNullishKind(node.left);
|
|
91
|
+
const rightKind = getNullishKind(node.right);
|
|
92
|
+
|
|
93
|
+
if (Boolean(leftKind) === Boolean(rightKind)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
reference: leftKind ? node.right : node.left,
|
|
99
|
+
kind: node.operator.length === 2 ? 'nullish' : (leftKind ?? rightKind),
|
|
100
|
+
isTrueWhenNullish: nullishOperators.has(node.operator),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const checksNullAndUndefined = (left, right) =>
|
|
105
|
+
(left.kind === 'null' && right.kind === 'undefined')
|
|
106
|
+
|| (left.kind === 'undefined' && right.kind === 'null');
|
|
107
|
+
|
|
108
|
+
function getNullishTest(node, sourceCode) {
|
|
109
|
+
const binaryCheck = getNullishBinaryCheck(node);
|
|
110
|
+
|
|
111
|
+
if (binaryCheck?.kind === 'nullish') {
|
|
112
|
+
return binaryCheck;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (node.type !== 'LogicalExpression') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const left = getNullishBinaryCheck(node.left);
|
|
120
|
+
const right = getNullishBinaryCheck(node.right);
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
!left
|
|
124
|
+
|| !right
|
|
125
|
+
|| !isSameNode(left.reference, right.reference, sourceCode)
|
|
126
|
+
) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
node.operator === '||'
|
|
132
|
+
&& left.isTrueWhenNullish
|
|
133
|
+
&& right.isTrueWhenNullish
|
|
134
|
+
&& checksNullAndUndefined(left, right)
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
137
|
+
reference: left.reference,
|
|
138
|
+
isTrueWhenNullish: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
node.operator === '&&'
|
|
144
|
+
&& !left.isTrueWhenNullish
|
|
145
|
+
&& !right.isTrueWhenNullish
|
|
146
|
+
&& checksNullAndUndefined(left, right)
|
|
147
|
+
) {
|
|
148
|
+
return {
|
|
149
|
+
reference: left.reference,
|
|
150
|
+
isTrueWhenNullish: false,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Render `node` as an operand of a `&&` expression, adding parentheses when precedence requires it.
|
|
156
|
+
function getLogicalOperandText(node, property, context) {
|
|
157
|
+
let text = getParenthesizedText(node, context);
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
!isParenthesized(node, context)
|
|
161
|
+
&& shouldAddParenthesesToLogicalExpressionChild(node, {operator: '&&', property})
|
|
162
|
+
) {
|
|
163
|
+
text = `(${text})`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Render `!test` as the left operand of a `&&` expression, stripping a leading `!` when present.
|
|
170
|
+
function getNegatedTestText(test, context) {
|
|
171
|
+
if (
|
|
172
|
+
test.type === 'UnaryExpression'
|
|
173
|
+
&& test.operator === '!'
|
|
174
|
+
&& test.prefix
|
|
175
|
+
) {
|
|
176
|
+
return getLogicalOperandText(test.argument, 'left', context);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let text = getParenthesizedText(test, context);
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
!isParenthesized(test, context)
|
|
183
|
+
&& shouldAddParenthesesToUnaryExpressionArgument(test, '!')
|
|
184
|
+
) {
|
|
185
|
+
text = `(${text})`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return `!${text}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getConditionalExpressionChildText(node, context) {
|
|
192
|
+
let text = getParenthesizedText(node, context);
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
!isParenthesized(node, context)
|
|
196
|
+
&& shouldAddParenthesesToConditionalExpressionChild(node)
|
|
197
|
+
) {
|
|
198
|
+
text = `(${text})`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return text;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getConditionalExpressionProblem(conditionalExpression, context) {
|
|
205
|
+
const {test, consequent, alternate} = conditionalExpression;
|
|
206
|
+
const isAlternateEmpty = isEmptyObjectExpression(alternate);
|
|
207
|
+
const isConsequentEmpty = isEmptyObjectExpression(consequent);
|
|
208
|
+
|
|
209
|
+
if (isAlternateEmpty === isConsequentEmpty) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const keptBranch = isAlternateEmpty ? consequent : alternate;
|
|
214
|
+
const nullishTest = getNullishTest(test, context.sourceCode);
|
|
215
|
+
const hasCommentsInside = context.sourceCode.getCommentsInside(conditionalExpression).length > 0;
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
(
|
|
219
|
+
isAlternateEmpty
|
|
220
|
+
? isSameNode(test, keptBranch, context.sourceCode)
|
|
221
|
+
: (
|
|
222
|
+
test.type === 'UnaryExpression'
|
|
223
|
+
&& test.operator === '!'
|
|
224
|
+
&& test.prefix
|
|
225
|
+
&& isSameNode(test.argument, keptBranch, context.sourceCode)
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|| (
|
|
229
|
+
nullishTest
|
|
230
|
+
&& !hasCommentsInside
|
|
231
|
+
&& (nullishTest.isTrueWhenNullish ? !isAlternateEmpty : isAlternateEmpty)
|
|
232
|
+
&& isSameNode(nullishTest.reference, keptBranch, context.sourceCode)
|
|
233
|
+
)
|
|
234
|
+
) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const testText = isAlternateEmpty
|
|
239
|
+
? getLogicalOperandText(test, 'left', context)
|
|
240
|
+
: getNegatedTestText(test, context);
|
|
241
|
+
const keptBranchText = getLogicalOperandText(keptBranch, 'right', context);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
node: conditionalExpression,
|
|
245
|
+
messageId: MESSAGE_ID,
|
|
246
|
+
data: {
|
|
247
|
+
expectedStyle: 'logical',
|
|
248
|
+
},
|
|
249
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
250
|
+
* fix(fixer, {abort}) {
|
|
251
|
+
if (hasCommentsInside) {
|
|
252
|
+
return abort();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
yield fixer.replaceText(conditionalExpression, `${testText} && ${keptBranchText}`);
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getLogicalExpressionProblem(logicalExpression, context) {
|
|
261
|
+
if (
|
|
262
|
+
logicalExpression.operator !== '&&'
|
|
263
|
+
|| isEmptyObjectExpression(logicalExpression.right)
|
|
264
|
+
) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (isSameNode(logicalExpression.left, logicalExpression.right, context.sourceCode)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const nullishTest = getNullishTest(logicalExpression.left, context.sourceCode);
|
|
273
|
+
|
|
274
|
+
if (
|
|
275
|
+
nullishTest
|
|
276
|
+
&& !nullishTest.isTrueWhenNullish
|
|
277
|
+
&& isSameNode(nullishTest.reference, logicalExpression.right, context.sourceCode)
|
|
278
|
+
) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const testText = getConditionalExpressionChildText(logicalExpression.left, context);
|
|
283
|
+
const consequentText = getConditionalExpressionChildText(logicalExpression.right, context);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
node: logicalExpression,
|
|
287
|
+
messageId: MESSAGE_ID,
|
|
288
|
+
data: {
|
|
289
|
+
expectedStyle: 'ternary',
|
|
290
|
+
},
|
|
291
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
292
|
+
* fix(fixer, {abort}) {
|
|
293
|
+
if (context.sourceCode.getCommentsInside(logicalExpression).length > 0) {
|
|
294
|
+
return abort();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
yield fixer.replaceText(logicalExpression, `${testText} ? ${consequentText} : {}`);
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
303
|
+
const create = context => {
|
|
304
|
+
const style = context.options[0];
|
|
305
|
+
|
|
306
|
+
if (style === STYLE_TERNARY) {
|
|
307
|
+
context.on('LogicalExpression', logicalExpression => {
|
|
308
|
+
if (!isObjectSpreadArgument(logicalExpression)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return getLogicalExpressionProblem(logicalExpression, context);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
context.on('ConditionalExpression', conditionalExpression => {
|
|
319
|
+
if (!isObjectSpreadArgument(conditionalExpression)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return getConditionalExpressionProblem(conditionalExpression, context);
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
328
|
+
const config = {
|
|
329
|
+
create,
|
|
330
|
+
meta: {
|
|
331
|
+
type: 'suggestion',
|
|
332
|
+
docs: {
|
|
333
|
+
description: 'Enforce consistent conditional object spread style.',
|
|
334
|
+
recommended: true,
|
|
335
|
+
},
|
|
336
|
+
fixable: 'code',
|
|
337
|
+
schema: [
|
|
338
|
+
{
|
|
339
|
+
description: 'The conditional object spread style to enforce.',
|
|
340
|
+
enum: [
|
|
341
|
+
STYLE_LOGICAL,
|
|
342
|
+
STYLE_TERNARY,
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
defaultOptions: [STYLE_LOGICAL],
|
|
347
|
+
messages,
|
|
348
|
+
languages: [
|
|
349
|
+
'js/js',
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default config;
|
|
@@ -66,6 +66,11 @@ const shouldIgnoreMemberExpression = node =>
|
|
|
66
66
|
isCallOrNewExpression(node.parent)
|
|
67
67
|
&& node.parent.callee === node
|
|
68
68
|
)
|
|
69
|
+
// Replacing the tag would change the `this` binding, like a method call.
|
|
70
|
+
|| (
|
|
71
|
+
node.parent.type === 'TaggedTemplateExpression'
|
|
72
|
+
&& node.parent.tag === node
|
|
73
|
+
)
|
|
69
74
|
|| isLeftHandSide(node);
|
|
70
75
|
|
|
71
76
|
const isRootVariableReassigned = (declaration, memberExpressionNode, memberScope, sourceCode) => {
|
|
@@ -92,14 +92,8 @@ function isUtf8Encoding(node, scope) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const {value} = staticValue;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
&& isUtf8EncodingString(value.encoding)
|
|
98
|
-
) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return false;
|
|
95
|
+
return Boolean(isSingleEncodingOptionObject(value)
|
|
96
|
+
&& isUtf8EncodingString(value.encoding));
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
function isBufferEncoding(node, scope) {
|
|
@@ -113,14 +107,8 @@ function isBufferEncoding(node, scope) {
|
|
|
113
107
|
return true;
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
&& (value.encoding === undefined || value.encoding === null)
|
|
119
|
-
) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return false;
|
|
110
|
+
return Boolean(isSingleEncodingOptionObject(value)
|
|
111
|
+
&& (value.encoding === undefined || value.encoding === null));
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
function isJsonReadFileCall(node, scope) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
getMemberAccessOperatorRange,
|
|
3
|
+
hasCommentInRange,
|
|
3
4
|
isSameReference,
|
|
4
5
|
unwrapTypeScriptExpression,
|
|
5
6
|
} from './utils/index.js';
|
|
@@ -60,28 +61,10 @@ function isSupportedMemberBase(node) {
|
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function getMemberAccessOperatorRange(memberExpression, context) {
|
|
64
|
-
const {sourceCode} = context;
|
|
65
|
-
const [, start] = getParenthesizedRange(memberExpression.object, context);
|
|
66
|
-
const end = memberExpression.computed
|
|
67
|
-
? sourceCode.getRange(sourceCode.getTokenBefore(memberExpression.property, token => token.value === '['))[1]
|
|
68
|
-
: sourceCode.getRange(memberExpression.property)[0];
|
|
69
|
-
|
|
70
|
-
return [start, end];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function hasCommentInRange(sourceCode, [start, end]) {
|
|
74
|
-
return sourceCode.getAllComments().some(comment => {
|
|
75
|
-
const [commentStart, commentEnd] = sourceCode.getRange(comment);
|
|
76
|
-
|
|
77
|
-
return commentStart >= start && commentEnd <= end;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
64
|
function canReplaceMemberAccessOperator(memberExpression, context) {
|
|
82
65
|
const range = getMemberAccessOperatorRange(memberExpression, context);
|
|
83
66
|
|
|
84
|
-
return !hasCommentInRange(context
|
|
67
|
+
return !hasCommentInRange(context, range);
|
|
85
68
|
}
|
|
86
69
|
|
|
87
70
|
function replaceMemberAccessOperator({memberExpression, context, fixer, replacement}) {
|