eslint-plugin-unicorn 70.0.0 → 71.1.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/package.json +26 -25
- package/readme.md +5 -1
- package/rules/ast/call-or-new-expression.js +2 -8
- package/rules/ast/is-member-expression.js +2 -8
- package/rules/comment-content.js +4 -4
- package/rules/consistent-boolean-name.js +275 -29
- package/rules/consistent-compound-words.js +1 -2
- package/rules/consistent-json-file-read.js +2 -5
- package/rules/dom-node-dataset.js +3 -7
- package/rules/escape-case.js +4 -4
- package/rules/expiring-todo-comments.js +2 -2
- package/rules/explicit-length-check.js +3 -3
- package/rules/filename-case.js +4 -4
- package/rules/fix/remove-argument.js +34 -8
- package/rules/fix/remove-statement.js +1 -3
- package/rules/id-match.js +2 -2
- package/rules/isolated-functions.js +47 -7
- package/rules/name-replacements.js +1 -2
- package/rules/no-anonymous-default-export.js +1 -3
- package/rules/no-array-callback-reference.js +2 -10
- package/rules/no-chained-comparison.js +1 -1
- package/rules/no-computed-property-existence-check.js +1 -4
- package/rules/no-declarations-before-early-exit.js +6 -5
- package/rules/no-error-property-assignment.js +3 -3
- package/rules/no-for-loop.js +4 -4
- package/rules/no-keyword-prefix.js +2 -2
- package/rules/no-mismatched-map-key.js +13 -20
- package/rules/no-named-default.js +2 -5
- package/rules/no-negated-array-predicate.js +4 -11
- package/rules/no-negated-comparison.js +7 -10
- package/rules/no-negation-in-equality-check.js +2 -5
- package/rules/no-process-exit.js +6 -6
- package/rules/no-static-only-class.js +2 -8
- package/rules/no-thenable.js +9 -15
- package/rules/no-undeclared-class-members.js +1 -5
- package/rules/no-unnecessary-array-flat-map.js +6 -5
- package/rules/no-unnecessary-global-this.js +5 -9
- package/rules/no-unused-properties.js +0 -1
- package/rules/no-useless-coercion.js +1 -2
- package/rules/no-useless-undefined.js +2 -2
- package/rules/prefer-array-find.js +2 -5
- package/rules/prefer-bigint-literals.js +1 -0
- package/rules/prefer-boolean-return.js +20 -3
- package/rules/prefer-default-parameters.js +2 -2
- package/rules/prefer-else-if.js +5 -9
- package/rules/prefer-has-check.js +6 -3
- package/rules/prefer-https.js +3 -3
- package/rules/prefer-identifier-import-export-specifiers.js +1 -3
- package/rules/prefer-minimal-ternary.js +4 -4
- package/rules/prefer-object-define-properties.js +1 -3
- package/rules/prefer-observer-apis.js +2 -5
- package/rules/prefer-private-class-fields.js +1 -3
- package/rules/prefer-simple-sort-comparator.js +6 -6
- package/rules/prefer-single-object-destructuring.js +2 -5
- package/rules/prefer-single-replace.js +2 -2
- package/rules/prefer-spread.js +22 -32
- package/rules/prefer-string-pad-start-end.js +1 -6
- package/rules/prefer-string-repeat.js +16 -9
- package/rules/prefer-string-replace-all.js +1 -1
- package/rules/prefer-ternary.js +5 -5
- package/rules/prefer-toggle-attribute.js +3 -3
- package/rules/prefer-top-level-await.js +6 -1
- package/rules/prefer-uint8array-base64.js +3 -1
- package/rules/require-proxy-trap-boolean-return.js +2 -2
- package/rules/rule/to-eslint-create.js +1 -2
- package/rules/rule/to-eslint-listener.js +1 -3
- package/rules/rule/to-eslint-problem.js +1 -3
- package/rules/rule/to-eslint-rule-fixer.js +1 -3
- package/rules/rule/to-eslint-rule.js +1 -3
- package/rules/rule/to-eslint-rules.js +1 -3
- package/rules/rule/unicorn-context.js +1 -3
- package/rules/rule/unicorn-listeners.js +1 -3
- package/rules/shared/no-array-mutate-rule.js +1 -3
- package/rules/shared/regexp-escape.js +4 -4
- package/rules/utils/can-tokens-be-adjacent.js +95 -0
- package/rules/utils/escape-string.js +2 -8
- package/rules/utils/get-available-variable-name.js +2 -13
- package/rules/utils/get-precedence.js +112 -0
- package/rules/utils/get-token-store.js +1 -3
- package/rules/utils/global-reference-tracker.js +3 -6
- package/rules/utils/index.js +4 -0
- package/rules/utils/is-boolean.js +90 -32
- package/rules/utils/is-identifier-name.js +5 -0
- package/rules/utils/is-promise-type.js +1 -3
- package/rules/utils/is-react-hook-name.js +5 -0
- package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
- package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
- package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
- package/rules/utils/to-location.js +1 -3
package/rules/prefer-spread.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isTypeParameterType,
|
|
19
19
|
hasUnparenthesizedOptionalChainElement,
|
|
20
20
|
isTypeScriptExpressionWrapper,
|
|
21
|
+
isDefaultLibrarySymbol,
|
|
21
22
|
} from './utils/index.js';
|
|
22
23
|
import {removeMethodCall} from './fix/index.js';
|
|
23
24
|
import {
|
|
@@ -155,8 +156,8 @@ function fixConcat(node, context, fixableArguments) {
|
|
|
155
156
|
const array = node.callee.object;
|
|
156
157
|
const concatCallArguments = node.arguments;
|
|
157
158
|
const arrayParenthesizedRange = getParenthesizedRange(array, context);
|
|
158
|
-
const
|
|
159
|
-
const arrayHasTrailingComma =
|
|
159
|
+
const isArrayIsArrayLiteral = isArrayLiteral(array);
|
|
160
|
+
const arrayHasTrailingComma = isArrayIsArrayLiteral && isArrayLiteralHasTrailingComma(array, sourceCode);
|
|
160
161
|
|
|
161
162
|
const getArrayLiteralElementsText = (node, keepTrailingComma) => {
|
|
162
163
|
if (
|
|
@@ -204,7 +205,7 @@ function fixConcat(node, context, fixableArguments) {
|
|
|
204
205
|
return '';
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
if (
|
|
208
|
+
if (isArrayIsArrayLiteral) {
|
|
208
209
|
if (!isEmptyArrayExpression(array)) {
|
|
209
210
|
text = ` ${text}`;
|
|
210
211
|
|
|
@@ -243,7 +244,7 @@ function fixConcat(node, context, fixableArguments) {
|
|
|
243
244
|
return function * (fixer) {
|
|
244
245
|
// Fixed code always starts with `[`
|
|
245
246
|
if (
|
|
246
|
-
!
|
|
247
|
+
!isArrayIsArrayLiteral
|
|
247
248
|
&& needsSemicolon(sourceCode.getTokenBefore(node), context, '[')
|
|
248
249
|
) {
|
|
249
250
|
yield fixer.insertTextBefore(node, ';');
|
|
@@ -257,7 +258,7 @@ function fixConcat(node, context, fixableArguments) {
|
|
|
257
258
|
|
|
258
259
|
const text = getFixedText();
|
|
259
260
|
|
|
260
|
-
if (
|
|
261
|
+
if (isArrayIsArrayLiteral) {
|
|
261
262
|
const closingBracketToken = sourceCode.getLastToken(array);
|
|
262
263
|
yield fixer.insertTextBefore(closingBracketToken, text);
|
|
263
264
|
} else {
|
|
@@ -389,9 +390,6 @@ function isGlobalMemberExpression(node, objectName, propertyName, context) {
|
|
|
389
390
|
return staticValue?.value === propertyName;
|
|
390
391
|
}
|
|
391
392
|
|
|
392
|
-
const isDefaultLibrarySymbol = (symbol, program) =>
|
|
393
|
-
symbol?.declarations?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())) ?? false;
|
|
394
|
-
|
|
395
393
|
function isBufferModuleImport(definition) {
|
|
396
394
|
return (
|
|
397
395
|
definition?.type === 'ImportBinding'
|
|
@@ -1339,11 +1337,11 @@ const create = context => {
|
|
|
1339
1337
|
};
|
|
1340
1338
|
|
|
1341
1339
|
const fixableArguments = getConcatFixableArguments(node.arguments, scope, context);
|
|
1342
|
-
const
|
|
1340
|
+
const isReceiverSafeToSpread = !isArrayConstructorWithOneArgument(object, context);
|
|
1343
1341
|
|
|
1344
1342
|
if (fixableArguments.length > 0 || node.arguments.length === 0) {
|
|
1345
1343
|
if (
|
|
1346
|
-
|
|
1344
|
+
isReceiverSafeToSpread
|
|
1347
1345
|
&& !hasCommentsOutsideRanges(node, getConcatPreservedRanges(node, fixableArguments.length))
|
|
1348
1346
|
) {
|
|
1349
1347
|
problem.fix = fixConcat(node, context, fixableArguments);
|
|
@@ -1357,7 +1355,7 @@ const create = context => {
|
|
|
1357
1355
|
return problem;
|
|
1358
1356
|
}
|
|
1359
1357
|
|
|
1360
|
-
if (!
|
|
1358
|
+
if (!isReceiverSafeToSpread) {
|
|
1361
1359
|
return problem;
|
|
1362
1360
|
}
|
|
1363
1361
|
|
|
@@ -1422,17 +1420,13 @@ const create = context => {
|
|
|
1422
1420
|
|
|
1423
1421
|
// `array.slice()`
|
|
1424
1422
|
context.on('CallExpression', node => {
|
|
1425
|
-
if (!(
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
})
|
|
1433
|
-
&& !isArrayLiteral(node.callee.object)
|
|
1434
|
-
&& !hasUnparenthesizedOptionalChainElement(node.callee.object, context)
|
|
1435
|
-
)) {
|
|
1423
|
+
if (!isMethodCall(node, {
|
|
1424
|
+
method: 'slice',
|
|
1425
|
+
minimumArguments: 0,
|
|
1426
|
+
maximumArguments: 1,
|
|
1427
|
+
optionalCall: false,
|
|
1428
|
+
optionalMember: false,
|
|
1429
|
+
}) || isArrayLiteral(node.callee.object) || hasUnparenthesizedOptionalChainElement(node.callee.object, context)) {
|
|
1436
1430
|
return;
|
|
1437
1431
|
}
|
|
1438
1432
|
|
|
@@ -1465,16 +1459,12 @@ const create = context => {
|
|
|
1465
1459
|
|
|
1466
1460
|
// `array.toSpliced()`
|
|
1467
1461
|
context.on('CallExpression', node => {
|
|
1468
|
-
if (!(
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
})
|
|
1475
|
-
&& node.callee.object.type !== 'ArrayExpression'
|
|
1476
|
-
&& !hasUnparenthesizedOptionalChainElement(node.callee.object, context)
|
|
1477
|
-
)) {
|
|
1462
|
+
if (!isMethodCall(node, {
|
|
1463
|
+
method: 'toSpliced',
|
|
1464
|
+
argumentsLength: 0,
|
|
1465
|
+
optionalCall: false,
|
|
1466
|
+
optionalMember: false,
|
|
1467
|
+
}) || node.callee.object.type === 'ArrayExpression' || hasUnparenthesizedOptionalChainElement(node.callee.object, context)) {
|
|
1478
1468
|
return;
|
|
1479
1469
|
}
|
|
1480
1470
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {findVariable, getStaticValue} from '@eslint-community/eslint-utils';
|
|
2
|
-
import {isMethodCall} from './ast/index.js';
|
|
2
|
+
import {isMethodCall, isNumericLiteral} from './ast/index.js';
|
|
3
3
|
import {
|
|
4
4
|
getParenthesizedText,
|
|
5
5
|
isSameReference,
|
|
@@ -78,11 +78,6 @@ const hasCommentsInside = (node, context) => context.sourceCode.getCommentsInsid
|
|
|
78
78
|
|
|
79
79
|
const isSimpleTarget = node => node.type === 'Identifier';
|
|
80
80
|
|
|
81
|
-
const isNumericLiteral = node => (
|
|
82
|
-
node.type === 'Literal'
|
|
83
|
-
&& typeof node.value === 'number'
|
|
84
|
-
);
|
|
85
|
-
|
|
86
81
|
const isZeroLiteral = node => (
|
|
87
82
|
isNumericLiteral(node)
|
|
88
83
|
&& node.value === 0
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import jsesc from 'jsesc';
|
|
2
1
|
import {isStringLiteral, isDirective} from './ast/index.js';
|
|
3
2
|
import {fixSpaceAroundKeyword} from './fix/index.js';
|
|
4
3
|
import isJestInlineSnapshot from './shared/is-jest-inline-snapshot.js';
|
|
@@ -10,15 +9,23 @@ const messages = {
|
|
|
10
9
|
|
|
11
10
|
const singleWhitespace = /^\s$/u;
|
|
12
11
|
|
|
12
|
+
const namedEscapes = new Map([
|
|
13
|
+
['\t', String.raw`\t`],
|
|
14
|
+
['\n', String.raw`\n`],
|
|
15
|
+
['\r', String.raw`\r`],
|
|
16
|
+
['\f', String.raw`\f`],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
// Escape a single whitespace character into a readable single-quoted string literal.
|
|
13
20
|
function quoteWhitespace(character) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
21
|
+
if (character === ' ') {
|
|
22
|
+
return '\' \'';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const escape = namedEscapes.get(character)
|
|
26
|
+
?? String.raw`\u{${character.codePointAt(0).toString(16).toUpperCase()}}`;
|
|
27
|
+
|
|
28
|
+
return `'${escape}'`;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
function getRepeatedWhitespace(value, minimumRepetitions) {
|
|
@@ -40,7 +40,7 @@ function hasSafeGlobalStringReplacementFlags(flags) {
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const getUnicodeEscape = codePoint => String.raw`\u{${codePoint.toString(16)}}`;
|
|
43
|
+
const getUnicodeEscape = codePoint => String.raw`\u{${codePoint.toString(16).toUpperCase()}}`;
|
|
44
44
|
|
|
45
45
|
function getValueReplacement(node) {
|
|
46
46
|
const {kind, codePoint, raw} = node;
|
package/rules/prefer-ternary.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {hasSideEffect, findVariable} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {isBooleanLiteral} from './ast/index.js';
|
|
2
3
|
import {
|
|
3
4
|
needsSemicolon,
|
|
4
5
|
isSameReference,
|
|
@@ -13,7 +14,6 @@ const messageId = 'prefer-ternary';
|
|
|
13
14
|
const suggestionMessageId = 'prefer-ternary/suggestion';
|
|
14
15
|
|
|
15
16
|
const isTernary = node => node?.type === 'ConditionalExpression';
|
|
16
|
-
const isBooleanLiteral = node => node?.type === 'Literal' && typeof node.value === 'boolean';
|
|
17
17
|
|
|
18
18
|
function getNodeBody(node) {
|
|
19
19
|
/* c8 ignore next 3 */
|
|
@@ -57,7 +57,7 @@ const isMergeableAssignmentExpression = (consequent, alternate) =>
|
|
|
57
57
|
|
|
58
58
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
59
59
|
const create = context => {
|
|
60
|
-
const
|
|
60
|
+
const isOnlySingleLine = context.options[0] === 'only-single-line';
|
|
61
61
|
const {sourceCode} = context;
|
|
62
62
|
|
|
63
63
|
const getText = node => {
|
|
@@ -134,7 +134,7 @@ const create = context => {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (
|
|
137
|
-
|
|
137
|
+
isOnlySingleLine
|
|
138
138
|
&& [node.test, right].some(n => !isSingleLineNode(n, context))
|
|
139
139
|
) {
|
|
140
140
|
return;
|
|
@@ -161,7 +161,7 @@ const create = context => {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
if (
|
|
164
|
-
|
|
164
|
+
isOnlySingleLine
|
|
165
165
|
&& !isSingleLineNode(declarator.init, context)
|
|
166
166
|
) {
|
|
167
167
|
return;
|
|
@@ -245,7 +245,7 @@ const create = context => {
|
|
|
245
245
|
const alternate = getNodeBody(node.alternate);
|
|
246
246
|
|
|
247
247
|
if (
|
|
248
|
-
|
|
248
|
+
isOnlySingleLine
|
|
249
249
|
&& [consequent, alternate, node.test].some(node => !isSingleLineNode(node, context))
|
|
250
250
|
) {
|
|
251
251
|
return;
|
|
@@ -224,15 +224,15 @@ const create = context => {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
const setCall = consequentCall.method === 'setAttribute' ? consequentCall : alternateCall;
|
|
227
|
-
const
|
|
227
|
+
const isSetWhenTrue = consequentCall === setCall;
|
|
228
228
|
const hasAttributeCondition = getHasAttributeCondition(node.test, setCall, context);
|
|
229
229
|
|
|
230
230
|
if (hasAttributeCondition?.isKnownNonDom) {
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const shouldToggleWithoutForce = Boolean(hasAttributeCondition) &&
|
|
235
|
-
const conditionText = shouldToggleWithoutForce ? '' : getConditionText(node.test, context, !
|
|
234
|
+
const shouldToggleWithoutForce = Boolean(hasAttributeCondition) && isSetWhenTrue === hasAttributeCondition.isNegative;
|
|
235
|
+
const conditionText = shouldToggleWithoutForce ? '' : getConditionText(node.test, context, !isSetWhenTrue);
|
|
236
236
|
const hasSafeAttributeValue = setCall.hasEmptyAttributeValue || getStaticStringValue(setCall.attributeValue) !== undefined;
|
|
237
237
|
const shouldReportOnly = (Boolean(conditionText) && setCall.isOptional) || !hasSafeAttributeValue;
|
|
238
238
|
|
|
@@ -205,7 +205,12 @@ const isInPromiseMethods = node => {
|
|
|
205
205
|
|
|
206
206
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
207
207
|
function create(context) {
|
|
208
|
-
|
|
208
|
+
// Use the real file path so processors or code blocks cannot hide file-level opt-outs.
|
|
209
|
+
const filename = context.physicalFilename.toLowerCase();
|
|
210
|
+
if (
|
|
211
|
+
filename.endsWith('.cjs')
|
|
212
|
+
|| filename.endsWith('.svelte')
|
|
213
|
+
) {
|
|
209
214
|
return;
|
|
210
215
|
}
|
|
211
216
|
|
|
@@ -152,7 +152,9 @@ const config = {
|
|
|
152
152
|
type: 'suggestion',
|
|
153
153
|
docs: {
|
|
154
154
|
description: 'Prefer `Uint8Array#toBase64()` and `Uint8Array.fromBase64()` over `atob()`, `btoa()`, and `Buffer` base64 conversions.',
|
|
155
|
-
|
|
155
|
+
// eslint-disable-next-line no-warning-comments
|
|
156
|
+
// TODO: Enable in the `recommended` and `unopinionated` configs when targeting Node.js 26.
|
|
157
|
+
recommended: false,
|
|
156
158
|
},
|
|
157
159
|
hasSuggestions: true,
|
|
158
160
|
messages,
|
|
@@ -277,8 +277,8 @@ const create = context => {
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const
|
|
281
|
-
functionBodyAlwaysExits.set(body.parent,
|
|
280
|
+
const isAllUnreachable = [...currentSegments()].every(segment => !segment.reachable);
|
|
281
|
+
functionBodyAlwaysExits.set(body.parent, isAllUnreachable);
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
function * checkCallOrNewExpression(node) {
|
|
@@ -26,7 +26,7 @@ Convert Unicorn style of `create` to ESLint style
|
|
|
26
26
|
@param {UnicornCreate} unicornCreate
|
|
27
27
|
@returns {EslintCreate}
|
|
28
28
|
*/
|
|
29
|
-
function toEslintCreate(unicornCreate) {
|
|
29
|
+
export default function toEslintCreate(unicornCreate) {
|
|
30
30
|
if (wrappedFunctions.has(unicornCreate)) {
|
|
31
31
|
return unicornCreate;
|
|
32
32
|
}
|
|
@@ -45,5 +45,4 @@ function toEslintCreate(unicornCreate) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export default toEslintCreate;
|
|
49
48
|
export {markFunctionWrapped};
|
|
@@ -19,7 +19,7 @@ import toEslintProblem from './to-eslint-problem.js';
|
|
|
19
19
|
@param {UnicornRuleListen} listener
|
|
20
20
|
@returns {Listener}
|
|
21
21
|
*/
|
|
22
|
-
function toEslintListener(context, listener) {
|
|
22
|
+
export default function toEslintListener(context, listener) {
|
|
23
23
|
// Listener arguments can be `codePath, node` or `node`
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -38,5 +38,3 @@ function toEslintListener(context, listener) {
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
export default toEslintListener;
|
|
@@ -14,7 +14,7 @@ import toEslintFixer from './to-eslint-rule-fixer.js';
|
|
|
14
14
|
@param {UnicornProblem} unicornProblem
|
|
15
15
|
@returns {EslintProblem}
|
|
16
16
|
*/
|
|
17
|
-
function toEslintProblem(unicornProblem) {
|
|
17
|
+
export default function toEslintProblem(unicornProblem) {
|
|
18
18
|
const eslintProblem = {...unicornProblem};
|
|
19
19
|
|
|
20
20
|
if (unicornProblem.fix) {
|
|
@@ -34,5 +34,3 @@ function toEslintProblem(unicornProblem) {
|
|
|
34
34
|
|
|
35
35
|
return eslintProblem;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
export default toEslintProblem;
|
|
@@ -26,7 +26,7 @@ Convert Unicorn style fix function to ESLint style fix function
|
|
|
26
26
|
@param {UnicornRuleFixer} fix
|
|
27
27
|
@returns {ESLint.Rule.RuleFixer}
|
|
28
28
|
*/
|
|
29
|
-
function toEslintRuleFixer(fix) {
|
|
29
|
+
export default function toEslintRuleFixer(fix) {
|
|
30
30
|
/** @param {UnicornReportFixer} fixer */
|
|
31
31
|
return fixer => {
|
|
32
32
|
const unicornReport = fix(fixer, fixOptions);
|
|
@@ -45,5 +45,3 @@ function toEslintRuleFixer(fix) {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
export default toEslintRuleFixer;
|
|
@@ -19,7 +19,7 @@ Convert Unicorn rule to ESLint rule
|
|
|
19
19
|
@param {UnicornRule} unicornRule
|
|
20
20
|
@returns {ESLint.Rule.RuleModule}
|
|
21
21
|
*/
|
|
22
|
-
function toEslintRule(ruleId, unicornRule) {
|
|
22
|
+
export default function toEslintRule(ruleId, unicornRule) {
|
|
23
23
|
return {
|
|
24
24
|
meta: {
|
|
25
25
|
// If there is are, options add `[]` so ESLint can validate that no data is passed to the rule.
|
|
@@ -34,5 +34,3 @@ function toEslintRule(ruleId, unicornRule) {
|
|
|
34
34
|
create: toEslintCreate(unicornRule.create),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
export default toEslintRule;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import toEslintRule from './to-eslint-rule.js';
|
|
2
2
|
|
|
3
|
-
function toEslintRules(rules) {
|
|
3
|
+
export default function toEslintRules(rules) {
|
|
4
4
|
return Object.fromEntries(Object.entries(rules).map(([ruleId, rule]) => [
|
|
5
5
|
ruleId,
|
|
6
6
|
toEslintRule(ruleId, rule),
|
|
7
7
|
]));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export default toEslintRules;
|
|
11
|
-
|
|
@@ -18,7 +18,7 @@ Create a better `Context` object with `on` and `onExit` method to add listeners
|
|
|
18
18
|
@param {UnicornListeners} listeners
|
|
19
19
|
@returns {UnicornContext}
|
|
20
20
|
*/
|
|
21
|
-
function createUnicornContext(eslintContext, listeners) {
|
|
21
|
+
export default function createUnicornContext(eslintContext, listeners) {
|
|
22
22
|
/** @type {UnicornContext} */
|
|
23
23
|
const context = new Proxy(eslintContext, {
|
|
24
24
|
get(target, property, receiver) {
|
|
@@ -32,5 +32,3 @@ function createUnicornContext(eslintContext, listeners) {
|
|
|
32
32
|
|
|
33
33
|
return context;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
export default createUnicornContext;
|
|
@@ -4,7 +4,7 @@ import toEslintListener from './to-eslint-listener.js';
|
|
|
4
4
|
@import {EslintListers, ListenerType, Listener} from './to-eslint-create.js'
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
class UnicornListeners {
|
|
7
|
+
export default class UnicornListeners {
|
|
8
8
|
#context;
|
|
9
9
|
#listeners = new Map();
|
|
10
10
|
|
|
@@ -61,5 +61,3 @@ class UnicornListeners {
|
|
|
61
61
|
return eslintListeners;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
export default UnicornListeners;
|
|
@@ -50,7 +50,7 @@ const schema = [
|
|
|
50
50
|
},
|
|
51
51
|
];
|
|
52
52
|
|
|
53
|
-
function noArrayMutateRule(methodName) {
|
|
53
|
+
export default function noArrayMutateRule(methodName) {
|
|
54
54
|
const {
|
|
55
55
|
replacement,
|
|
56
56
|
predicate,
|
|
@@ -159,5 +159,3 @@ function noArrayMutateRule(methodName) {
|
|
|
159
159
|
|
|
160
160
|
return config;
|
|
161
161
|
}
|
|
162
|
-
|
|
163
|
-
export default noArrayMutateRule;
|
|
@@ -19,12 +19,12 @@ const getCharacterClassCharacters = pattern => {
|
|
|
19
19
|
|
|
20
20
|
for (let index = 0; index < characterClass.length; index++) {
|
|
21
21
|
let character = characterClass[index];
|
|
22
|
-
let
|
|
22
|
+
let isEscaped = false;
|
|
23
23
|
|
|
24
24
|
if (character === '\\') {
|
|
25
25
|
index++;
|
|
26
26
|
character = characterClass[index];
|
|
27
|
-
|
|
27
|
+
isEscaped = true;
|
|
28
28
|
|
|
29
29
|
if (!character) {
|
|
30
30
|
return;
|
|
@@ -33,7 +33,7 @@ const getCharacterClassCharacters = pattern => {
|
|
|
33
33
|
|
|
34
34
|
if (
|
|
35
35
|
character === '-'
|
|
36
|
-
&& !
|
|
36
|
+
&& !isEscaped
|
|
37
37
|
&& index !== 0
|
|
38
38
|
&& index !== characterClass.length - 1
|
|
39
39
|
) {
|
|
@@ -42,7 +42,7 @@ const getCharacterClassCharacters = pattern => {
|
|
|
42
42
|
|
|
43
43
|
if (
|
|
44
44
|
character === ']'
|
|
45
|
-
&& !
|
|
45
|
+
&& !isEscaped
|
|
46
46
|
&& index !== 0
|
|
47
47
|
) {
|
|
48
48
|
return;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@import * as ESLint from 'eslint';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const WORD_CHARACTER = /[\w$]/u;
|
|
6
|
+
const DECIMAL_INTEGER_LITERAL = /^\d(?:_?\d)*$/u;
|
|
7
|
+
const TRAILING_DECIMAL_INTEGER_LITERAL = /(?:^|[^\w$])\d(?:_?\d)*$/u;
|
|
8
|
+
const TRAILING_DIGIT_DOT = /\d\.$/u;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
@param {ESLint.AST.Token | string} value
|
|
12
|
+
@returns {string}
|
|
13
|
+
*/
|
|
14
|
+
function getText(value) {
|
|
15
|
+
return typeof value === 'string' ? value : value.value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
@param {ESLint.AST.Token | string} value
|
|
20
|
+
@returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function isLineCommentOrShebang(value) {
|
|
23
|
+
return typeof value === 'object' && ['Line', 'Shebang', 'Hashbang'].includes(value.type);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
@param {ESLint.AST.Token | string} value
|
|
28
|
+
@returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
function isComment(value) {
|
|
31
|
+
return typeof value === 'object' && ['Line', 'Block'].includes(value.type);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
@param {ESLint.AST.Token | string} value
|
|
36
|
+
@returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function isUnsafeNumericDotLeft(value) {
|
|
39
|
+
return typeof value === 'object' ? value.type === 'Numeric' && DECIMAL_INTEGER_LITERAL.test(value.value) : TRAILING_DECIMAL_INTEGER_LITERAL.test(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Checks whether `left` and `right` can be placed directly next to each other without the last character of `left` and the first character of `right` merging into a token that changes the meaning of the code (for example two identifiers becoming one, `+` and `+` becoming `++`, or `2` and `.2` becoming `2.2`).
|
|
44
|
+
|
|
45
|
+
This is a lightweight, character-boundary check, not a full tokenizer, inspired by ESLint's own (more thorough) `canTokensBeAdjacent`: https://github.com/eslint/eslint/blob/b23015955c8d6e6516076190730f538c86927f26/lib/rules/utils/ast-utils.js#L2522-L2529
|
|
46
|
+
It only guards against the adjacency hazards that come up when building fixer replacement text in this codebase: identifiers/keywords/numbers merging, `+`/`-` doubling into `++`/`--`, a numeric literal absorbing a following decimal point, and a `/` being swallowed into a following comment. It intentionally doesn't try to detect hazards that require a real tokenizer, like an already-open `//` comment inside `left`, or malformed token text.
|
|
47
|
+
|
|
48
|
+
@param {ESLint.AST.Token | string} left - The left token or text.
|
|
49
|
+
@param {ESLint.AST.Token | string} right - The right token or text.
|
|
50
|
+
@returns {boolean} `false` if a space is needed between `left` and `right` to keep them from merging. `true` if they can safely be adjacent.
|
|
51
|
+
*/
|
|
52
|
+
export default function canTokensBeAdjacent(left, right) {
|
|
53
|
+
if (isLineCommentOrShebang(left)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const leftText = getText(left);
|
|
58
|
+
|
|
59
|
+
if (leftText === '') {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const rightText = getText(right);
|
|
64
|
+
const lastCharacter = leftText.at(-1);
|
|
65
|
+
|
|
66
|
+
if (lastCharacter === '/' && isComment(right)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (rightText === '') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const firstCharacter = rightText[0];
|
|
75
|
+
|
|
76
|
+
if (WORD_CHARACTER.test(lastCharacter) && WORD_CHARACTER.test(firstCharacter)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
(lastCharacter === '+' && firstCharacter === '+')
|
|
82
|
+
|| (lastCharacter === '-' && firstCharacter === '-')
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
(isUnsafeNumericDotLeft(left) && firstCharacter === '.')
|
|
89
|
+
|| (TRAILING_DIGIT_DOT.test(leftText) && WORD_CHARACTER.test(firstCharacter))
|
|
90
|
+
) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return !(lastCharacter === '/' && (firstCharacter === '/' || firstCharacter === '*'));
|
|
95
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import quoteJsString from 'quote-js-string';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
Escape string and wrap the result in quotes.
|
|
@@ -14,11 +14,5 @@ export default function escapeString(string, quote = '\'') {
|
|
|
14
14
|
}
|
|
15
15
|
/* c8 ignore end */
|
|
16
16
|
|
|
17
|
-
return
|
|
18
|
-
quotes: quote === '"' ? 'double' : 'single',
|
|
19
|
-
wrap: true,
|
|
20
|
-
es6: true,
|
|
21
|
-
minimal: true,
|
|
22
|
-
lowercaseHex: false,
|
|
23
|
-
});
|
|
17
|
+
return quoteJsString(string, quote);
|
|
24
18
|
}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isIdentifier from 'is-identifier';
|
|
2
2
|
import resolveVariableName from './resolve-variable-name.js';
|
|
3
3
|
import getReferences from './get-references.js';
|
|
4
4
|
|
|
5
|
-
const {
|
|
6
|
-
isIdentifierName,
|
|
7
|
-
isStrictReservedWord,
|
|
8
|
-
isKeyword,
|
|
9
|
-
} = helperValidatorIdentifier;
|
|
10
|
-
|
|
11
5
|
// https://github.com/microsoft/TypeScript/issues/2536#issuecomment-87194347
|
|
12
6
|
const typescriptReservedWords = new Set([
|
|
13
7
|
'break',
|
|
@@ -72,14 +66,9 @@ const typescriptReservedWords = new Set([
|
|
|
72
66
|
'of',
|
|
73
67
|
]);
|
|
74
68
|
|
|
75
|
-
// Copied from https://github.com/babel/babel/blob/fce35af69101c6b316557e28abf60bdbf77d6a36/packages/babel-types/src/validators/isValidIdentifier.ts#L7
|
|
76
|
-
// Use this function instead of `require('@babel/types').isIdentifier`, since `@babel/helper-validator-identifier` package is much smaller
|
|
77
69
|
const isValidIdentifier = name =>
|
|
78
70
|
typeof name === 'string'
|
|
79
|
-
&&
|
|
80
|
-
&& !isStrictReservedWord(name, true)
|
|
81
|
-
&& isIdentifierName(name)
|
|
82
|
-
&& name !== 'arguments'
|
|
71
|
+
&& isIdentifier(name)
|
|
83
72
|
&& !typescriptReservedWords.has(name);
|
|
84
73
|
|
|
85
74
|
/*
|