eslint-plugin-unicorn 67.0.0 → 69.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 +8 -3
- package/readme.md +165 -16
- package/rules/ast/is-empty-node.js +1 -5
- package/rules/ast/is-empty-object-expression.js +2 -2
- package/rules/better-dom-traversing.js +17 -8
- package/rules/class-reference-in-static-methods.js +14 -2
- package/rules/comment-content.js +136 -18
- package/rules/consistent-assert.js +1 -1
- package/rules/consistent-boolean-name.js +44 -5
- package/rules/consistent-compound-words.js +7 -5
- package/rules/consistent-conditional-object-spread.js +360 -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/consistent-tuple-labels.js +49 -0
- package/rules/custom-error-definition.js +26 -7
- 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 +50 -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-computed-property-existence-check.js +6 -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 +106 -43
- package/rules/no-instanceof-builtins.js +73 -14
- 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-invalid-well-known-symbol-methods.js +205 -0
- package/rules/no-keyword-prefix.js +14 -4
- package/rules/no-late-current-target-access.js +13 -107
- package/rules/no-late-event-control.js +127 -0
- 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 +1383 -0
- package/rules/no-object-methods-with-collections.js +4 -289
- package/rules/no-process-exit.js +16 -12
- package/rules/no-redundant-comparison.js +5 -12
- package/rules/no-return-array-push.js +20 -5
- package/rules/no-selector-as-dom-name.js +150 -0
- package/rules/no-thenable.js +27 -11
- package/rules/no-top-level-side-effects.js +8 -1
- 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-global-this.js +36 -1
- package/rules/no-unnecessary-splice.js +15 -20
- package/rules/no-unreadable-for-of-expression.js +2 -1
- package/rules/no-unreadable-object-destructuring.js +15 -0
- package/rules/no-unsafe-property-key.js +10 -15
- package/rules/no-unsafe-string-replacement.js +31 -0
- package/rules/no-unused-array-method-return.js +11 -2
- package/rules/no-unused-properties.js +4 -3
- package/rules/no-useless-boolean-cast.js +58 -1
- package/rules/no-useless-coercion.js +71 -15
- package/rules/no-useless-compound-assignment.js +110 -0
- package/rules/no-useless-concat.js +27 -3
- package/rules/no-useless-delete-check.js +353 -0
- package/rules/no-useless-else.js +27 -62
- 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-iterator-to-array.js +165 -115
- 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-abort-signal-timeout.js +395 -0
- package/rules/prefer-aggregate-error.js +478 -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 +6 -30
- package/rules/prefer-await.js +29 -0
- package/rules/prefer-boolean-return.js +224 -0
- package/rules/prefer-code-point.js +58 -8
- package/rules/prefer-continue.js +377 -0
- package/rules/prefer-default-parameters.js +10 -6
- package/rules/prefer-dispose.js +1 -5
- package/rules/prefer-dom-node-replace-children.js +353 -0
- package/rules/prefer-early-return.js +160 -30
- package/rules/prefer-else-if.js +50 -53
- package/rules/prefer-error-is-error.js +181 -0
- 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-has-check.js +1 -4
- package/rules/prefer-hoisting-branch-code.js +424 -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-iterator-concat.js +21 -1
- 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 +41 -13
- package/rules/prefer-modern-dom-apis.js +1 -45
- 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-observer-apis.js +498 -0
- package/rules/prefer-optional-catch-binding.js +30 -24
- package/rules/prefer-private-class-fields.js +2 -8
- package/rules/prefer-promise-try.js +238 -0
- 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-methods.js +337 -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-toggle-attribute.js +293 -0
- 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-url-search-parameters.js +411 -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/late-event-handler.js +82 -0
- package/rules/shared/{abbreviations.js → name-replacements.js} +63 -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/builtin-collection-type.js +267 -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 +38 -2
- package/rules/utils/is-boolean.js +26 -1
- package/rules/utils/is-event.js +127 -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/should-report-replace-children-receiver.js +210 -0
- package/rules/utils/track-branch-exits.js +79 -0
- package/rules/utils/type-helpers.js +166 -8
- package/rules/utils/types.js +22 -0
|
@@ -0,0 +1,360 @@
|
|
|
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
|
+
// `{...{}}`, `{...undefined}`, and `{...null}` all spread nothing.
|
|
26
|
+
const isEmptySpreadBranch = node =>
|
|
27
|
+
isEmptyObjectExpression(node)
|
|
28
|
+
|| isUndefined(node)
|
|
29
|
+
|| isNullLiteral(node);
|
|
30
|
+
|
|
31
|
+
const isObjectSpreadArgument = node => (
|
|
32
|
+
node.parent.type === 'SpreadElement'
|
|
33
|
+
&& node.parent.argument === node
|
|
34
|
+
&& node.parent.parent.type === 'ObjectExpression'
|
|
35
|
+
&& node.parent.parent.properties.includes(node.parent)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function isSameNode(left, right, sourceCode) {
|
|
39
|
+
if (isSameReference(left, right)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (left.type !== right.type) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch (left.type) {
|
|
48
|
+
case 'AwaitExpression': {
|
|
49
|
+
return isSameNode(left.argument, right.argument, sourceCode);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'LogicalExpression': {
|
|
53
|
+
return (
|
|
54
|
+
left.operator === right.operator
|
|
55
|
+
&& isSameNode(left.left, right.left, sourceCode)
|
|
56
|
+
&& isSameNode(left.right, right.right, sourceCode)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
case 'UnaryExpression': {
|
|
61
|
+
return (
|
|
62
|
+
left.operator === right.operator
|
|
63
|
+
&& left.prefix === right.prefix
|
|
64
|
+
&& isSameNode(left.argument, right.argument, sourceCode)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'UpdateExpression': {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// No default
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return sourceCode.getText(left) === sourceCode.getText(right);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getNullishKind(node) {
|
|
79
|
+
if (isNullLiteral(node)) {
|
|
80
|
+
return 'null';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isUndefined(node)) {
|
|
84
|
+
return 'undefined';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getNullishBinaryCheck(node) {
|
|
89
|
+
if (
|
|
90
|
+
node.type !== 'BinaryExpression'
|
|
91
|
+
|| (!nullishOperators.has(node.operator) && !nonNullishOperators.has(node.operator))
|
|
92
|
+
) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const leftKind = getNullishKind(node.left);
|
|
97
|
+
const rightKind = getNullishKind(node.right);
|
|
98
|
+
|
|
99
|
+
if (Boolean(leftKind) === Boolean(rightKind)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
reference: leftKind ? node.right : node.left,
|
|
105
|
+
kind: node.operator.length === 2 ? 'nullish' : (leftKind ?? rightKind),
|
|
106
|
+
isTrueWhenNullish: nullishOperators.has(node.operator),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const checksNullAndUndefined = (left, right) =>
|
|
111
|
+
(left.kind === 'null' && right.kind === 'undefined')
|
|
112
|
+
|| (left.kind === 'undefined' && right.kind === 'null');
|
|
113
|
+
|
|
114
|
+
function getNullishTest(node, sourceCode) {
|
|
115
|
+
const binaryCheck = getNullishBinaryCheck(node);
|
|
116
|
+
|
|
117
|
+
if (binaryCheck?.kind === 'nullish') {
|
|
118
|
+
return binaryCheck;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (node.type !== 'LogicalExpression') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const left = getNullishBinaryCheck(node.left);
|
|
126
|
+
const right = getNullishBinaryCheck(node.right);
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
!left
|
|
130
|
+
|| !right
|
|
131
|
+
|| !isSameNode(left.reference, right.reference, sourceCode)
|
|
132
|
+
) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
node.operator === '||'
|
|
138
|
+
&& left.isTrueWhenNullish
|
|
139
|
+
&& right.isTrueWhenNullish
|
|
140
|
+
&& checksNullAndUndefined(left, right)
|
|
141
|
+
) {
|
|
142
|
+
return {
|
|
143
|
+
reference: left.reference,
|
|
144
|
+
isTrueWhenNullish: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
node.operator === '&&'
|
|
150
|
+
&& !left.isTrueWhenNullish
|
|
151
|
+
&& !right.isTrueWhenNullish
|
|
152
|
+
&& checksNullAndUndefined(left, right)
|
|
153
|
+
) {
|
|
154
|
+
return {
|
|
155
|
+
reference: left.reference,
|
|
156
|
+
isTrueWhenNullish: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Render `node` as an operand of a `&&` expression, adding parentheses when precedence requires it.
|
|
162
|
+
function getLogicalOperandText(node, property, context) {
|
|
163
|
+
let text = getParenthesizedText(node, context);
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
!isParenthesized(node, context)
|
|
167
|
+
&& shouldAddParenthesesToLogicalExpressionChild(node, {operator: '&&', property})
|
|
168
|
+
) {
|
|
169
|
+
text = `(${text})`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return text;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Render `!test` as the left operand of a `&&` expression, stripping a leading `!` when present.
|
|
176
|
+
function getNegatedTestText(test, context) {
|
|
177
|
+
if (
|
|
178
|
+
test.type === 'UnaryExpression'
|
|
179
|
+
&& test.operator === '!'
|
|
180
|
+
&& test.prefix
|
|
181
|
+
) {
|
|
182
|
+
return getLogicalOperandText(test.argument, 'left', context);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let text = getParenthesizedText(test, context);
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
!isParenthesized(test, context)
|
|
189
|
+
&& shouldAddParenthesesToUnaryExpressionArgument(test, '!')
|
|
190
|
+
) {
|
|
191
|
+
text = `(${text})`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return `!${text}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getConditionalExpressionChildText(node, context) {
|
|
198
|
+
let text = getParenthesizedText(node, context);
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
!isParenthesized(node, context)
|
|
202
|
+
&& shouldAddParenthesesToConditionalExpressionChild(node)
|
|
203
|
+
) {
|
|
204
|
+
text = `(${text})`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return text;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getConditionalExpressionProblem(conditionalExpression, context) {
|
|
211
|
+
const {test, consequent, alternate} = conditionalExpression;
|
|
212
|
+
const isAlternateEmpty = isEmptySpreadBranch(alternate);
|
|
213
|
+
const isConsequentEmpty = isEmptySpreadBranch(consequent);
|
|
214
|
+
|
|
215
|
+
if (isAlternateEmpty === isConsequentEmpty) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const keptBranch = isAlternateEmpty ? consequent : alternate;
|
|
220
|
+
const nullishTest = getNullishTest(test, context.sourceCode);
|
|
221
|
+
const hasCommentsInside = context.sourceCode.getCommentsInside(conditionalExpression).length > 0;
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
(
|
|
225
|
+
isAlternateEmpty
|
|
226
|
+
? isSameNode(test, keptBranch, context.sourceCode)
|
|
227
|
+
: (
|
|
228
|
+
test.type === 'UnaryExpression'
|
|
229
|
+
&& test.operator === '!'
|
|
230
|
+
&& test.prefix
|
|
231
|
+
&& isSameNode(test.argument, keptBranch, context.sourceCode)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|| (
|
|
235
|
+
nullishTest
|
|
236
|
+
&& !hasCommentsInside
|
|
237
|
+
&& (nullishTest.isTrueWhenNullish ? !isAlternateEmpty : isAlternateEmpty)
|
|
238
|
+
&& isSameNode(nullishTest.reference, keptBranch, context.sourceCode)
|
|
239
|
+
)
|
|
240
|
+
) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const testText = isAlternateEmpty
|
|
245
|
+
? getLogicalOperandText(test, 'left', context)
|
|
246
|
+
: getNegatedTestText(test, context);
|
|
247
|
+
const keptBranchText = getLogicalOperandText(keptBranch, 'right', context);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
node: conditionalExpression,
|
|
251
|
+
messageId: MESSAGE_ID,
|
|
252
|
+
data: {
|
|
253
|
+
expectedStyle: 'logical',
|
|
254
|
+
},
|
|
255
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
256
|
+
* fix(fixer, {abort}) {
|
|
257
|
+
if (hasCommentsInside) {
|
|
258
|
+
return abort();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
yield fixer.replaceText(conditionalExpression, `${testText} && ${keptBranchText}`);
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getLogicalExpressionProblem(logicalExpression, context) {
|
|
267
|
+
if (
|
|
268
|
+
logicalExpression.operator !== '&&'
|
|
269
|
+
|| isEmptySpreadBranch(logicalExpression.right)
|
|
270
|
+
) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (isSameNode(logicalExpression.left, logicalExpression.right, context.sourceCode)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const nullishTest = getNullishTest(logicalExpression.left, context.sourceCode);
|
|
279
|
+
|
|
280
|
+
if (
|
|
281
|
+
nullishTest
|
|
282
|
+
&& !nullishTest.isTrueWhenNullish
|
|
283
|
+
&& isSameNode(nullishTest.reference, logicalExpression.right, context.sourceCode)
|
|
284
|
+
) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const testText = getConditionalExpressionChildText(logicalExpression.left, context);
|
|
289
|
+
const consequentText = getConditionalExpressionChildText(logicalExpression.right, context);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
node: logicalExpression,
|
|
293
|
+
messageId: MESSAGE_ID,
|
|
294
|
+
data: {
|
|
295
|
+
expectedStyle: 'ternary',
|
|
296
|
+
},
|
|
297
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
298
|
+
* fix(fixer, {abort}) {
|
|
299
|
+
if (context.sourceCode.getCommentsInside(logicalExpression).length > 0) {
|
|
300
|
+
return abort();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
yield fixer.replaceText(logicalExpression, `${testText} ? ${consequentText} : {}`);
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
309
|
+
const create = context => {
|
|
310
|
+
const style = context.options[0];
|
|
311
|
+
|
|
312
|
+
if (style === STYLE_TERNARY) {
|
|
313
|
+
context.on('LogicalExpression', logicalExpression => {
|
|
314
|
+
if (!isObjectSpreadArgument(logicalExpression)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return getLogicalExpressionProblem(logicalExpression, context);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
context.on('ConditionalExpression', conditionalExpression => {
|
|
325
|
+
if (!isObjectSpreadArgument(conditionalExpression)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return getConditionalExpressionProblem(conditionalExpression, context);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
334
|
+
const config = {
|
|
335
|
+
create,
|
|
336
|
+
meta: {
|
|
337
|
+
type: 'suggestion',
|
|
338
|
+
docs: {
|
|
339
|
+
description: 'Enforce consistent conditional object spread style.',
|
|
340
|
+
recommended: true,
|
|
341
|
+
},
|
|
342
|
+
fixable: 'code',
|
|
343
|
+
schema: [
|
|
344
|
+
{
|
|
345
|
+
description: 'The conditional object spread style to enforce.',
|
|
346
|
+
enum: [
|
|
347
|
+
STYLE_LOGICAL,
|
|
348
|
+
STYLE_TERNARY,
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
defaultOptions: [STYLE_LOGICAL],
|
|
353
|
+
messages,
|
|
354
|
+
languages: [
|
|
355
|
+
'js/js',
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
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}) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const MESSAGE_ID = 'consistent-tuple-labels';
|
|
2
|
+
const messages = {
|
|
3
|
+
[MESSAGE_ID]: 'This tuple element should have a label, just like the other elements.',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// A rest element keeps its label inside the `TSRestType` wrapper, e.g. `[...rest: number[]]`.
|
|
7
|
+
const isLabeledElement = element =>
|
|
8
|
+
element.type === 'TSNamedTupleMember'
|
|
9
|
+
|| (element.type === 'TSRestType' && element.typeAnnotation.type === 'TSNamedTupleMember');
|
|
10
|
+
|
|
11
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
12
|
+
const create = context => {
|
|
13
|
+
context.on('TSTupleType', node => {
|
|
14
|
+
const elements = node.elementTypes;
|
|
15
|
+
|
|
16
|
+
// A tuple with fewer than two elements can't be inconsistent.
|
|
17
|
+
if (elements.length < 2) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const unlabeledElements = elements.filter(element => !isLabeledElement(element));
|
|
22
|
+
|
|
23
|
+
// All labeled or all unlabeled is consistent.
|
|
24
|
+
if (unlabeledElements.length === 0 || unlabeledElements.length === elements.length) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// At least one element is labeled, so flag every unlabeled element.
|
|
29
|
+
return unlabeledElements.map(element => ({node: element, messageId: MESSAGE_ID}));
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
34
|
+
const config = {
|
|
35
|
+
create,
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'suggestion',
|
|
38
|
+
docs: {
|
|
39
|
+
description: 'Enforce consistent labels on tuple type elements.',
|
|
40
|
+
recommended: true,
|
|
41
|
+
},
|
|
42
|
+
messages,
|
|
43
|
+
languages: [
|
|
44
|
+
'js/js',
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default config;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import {getPropertyName} from '@eslint-community/eslint-utils';
|
|
1
2
|
import {
|
|
2
3
|
upperFirst,
|
|
3
4
|
getParenthesizedText,
|
|
4
5
|
isNodeMatchesNameOrPath,
|
|
5
6
|
} from './utils/index.js';
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getStaticStringValue,
|
|
9
|
+
isUndefined,
|
|
10
|
+
} from './ast/index.js';
|
|
7
11
|
|
|
8
12
|
const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
|
|
9
13
|
const MESSAGE_ID_DO_NOT_PASS_MESSAGE_TO_SUPER = 'doNotPassMessageToSuper';
|
|
@@ -73,8 +77,7 @@ const isPropertyDefinition = (node, name) =>
|
|
|
73
77
|
&& node.key.name === name;
|
|
74
78
|
|
|
75
79
|
const isValidNameProperty = (nameProperty, className) =>
|
|
76
|
-
nameProperty?.value
|
|
77
|
-
&& nameProperty.value.value === className;
|
|
80
|
+
getStaticStringValue(nameProperty?.value) === className;
|
|
78
81
|
|
|
79
82
|
const isMessageAccessor = (node, kind) =>
|
|
80
83
|
node.type === 'MethodDefinition'
|
|
@@ -200,6 +203,20 @@ const isSameIdentifier = (node, identifier) =>
|
|
|
200
203
|
node?.type === 'Identifier'
|
|
201
204
|
&& node.name === identifier.name;
|
|
202
205
|
|
|
206
|
+
// Whether `super()` already forwards the error options inline, e.g. `super('Fixed message', {cause})`.
|
|
207
|
+
const hasInlineErrorOptions = (superCallExpression, shouldPassMessageToSuper) => {
|
|
208
|
+
if (shouldPassMessageToSuper) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const [messageArgument, optionsArgument] = superCallExpression.arguments;
|
|
213
|
+
return superCallExpression.arguments.length === 2
|
|
214
|
+
&& getStaticStringValue(messageArgument) !== undefined
|
|
215
|
+
&& optionsArgument.type === 'ObjectExpression'
|
|
216
|
+
&& optionsArgument.properties.length === 1
|
|
217
|
+
&& getPropertyName(optionsArgument.properties[0]) === 'cause';
|
|
218
|
+
};
|
|
219
|
+
|
|
203
220
|
const getErrorOptionsProblem = (context, constructor, superExpression, hasMessageAccessor) => {
|
|
204
221
|
const parameters = constructor.value.params;
|
|
205
222
|
const firstParameter = parameters[0];
|
|
@@ -236,6 +253,11 @@ const getErrorOptionsProblem = (context, constructor, superExpression, hasMessag
|
|
|
236
253
|
const messageArgumentText = shouldPassMessageToSuper ? firstParameterIdentifier.name : 'undefined';
|
|
237
254
|
|
|
238
255
|
if (!optionsParameter) {
|
|
256
|
+
// When `options` is already forwarded to `super()` (e.g. `super('Fixed message', {cause})`), a dedicated `options` parameter isn't needed.
|
|
257
|
+
if (hasInlineErrorOptions(superCallExpression, shouldPassMessageToSuper)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
239
261
|
return {
|
|
240
262
|
node: firstParameter,
|
|
241
263
|
messageId: MESSAGE_ID_MISSING_OPTIONS_PARAMETER,
|
|
@@ -282,10 +304,7 @@ function getInvalidErrorNameProblem(constructorBodyNode, constructorBody, errorD
|
|
|
282
304
|
return;
|
|
283
305
|
}
|
|
284
306
|
|
|
285
|
-
if (
|
|
286
|
-
nameExpression.expression.right.type !== 'Literal'
|
|
287
|
-
|| nameExpression.expression.right.value !== name
|
|
288
|
-
) {
|
|
307
|
+
if (getStaticStringValue(nameExpression.expression.right) !== name) {
|
|
289
308
|
return createInvalidNameError(nameExpression.expression.right ?? constructorBodyNode, name);
|
|
290
309
|
}
|
|
291
310
|
}
|