eslint-plugin-unicorn 63.0.0 → 65.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.
Files changed (146) hide show
  1. package/index.js +26 -18
  2. package/package.json +32 -30
  3. package/readme.md +183 -147
  4. package/rules/ast/call-or-new-expression.js +3 -5
  5. package/rules/ast/literal.js +1 -6
  6. package/rules/better-dom-traversing.js +291 -0
  7. package/rules/catch-error-name.js +3 -3
  8. package/rules/consistent-compound-words.js +452 -0
  9. package/rules/consistent-destructuring.js +234 -22
  10. package/rules/consistent-function-scoping.js +35 -11
  11. package/rules/consistent-json-file-read.js +245 -0
  12. package/rules/consistent-template-literal-escape.js +52 -0
  13. package/rules/custom-error-definition.js +176 -71
  14. package/rules/dom-node-dataset.js +431 -0
  15. package/rules/escape-case.js +2 -1
  16. package/rules/expiring-todo-comments.js +56 -102
  17. package/rules/explicit-length-check.js +145 -33
  18. package/rules/filename-case.js +97 -13
  19. package/rules/fix/append-argument.js +1 -1
  20. package/rules/fix/remove-expression-statement.js +1 -0
  21. package/rules/import-style.js +30 -10
  22. package/rules/index.js +37 -3
  23. package/rules/isolated-functions.js +11 -5
  24. package/rules/no-accessor-recursion.js +1 -2
  25. package/rules/no-anonymous-default-export.js +9 -11
  26. package/rules/no-array-callback-reference.js +148 -22
  27. package/rules/no-array-fill-with-reference-type.js +127 -0
  28. package/rules/no-array-for-each.js +18 -3
  29. package/rules/no-array-from-fill.js +76 -0
  30. package/rules/no-array-method-this-argument.js +1 -1
  31. package/rules/no-array-reduce.js +509 -2
  32. package/rules/no-blob-to-file.js +299 -0
  33. package/rules/no-canvas-to-image.js +152 -0
  34. package/rules/no-confusing-array-splice.js +164 -0
  35. package/rules/no-duplicate-set-values.js +57 -0
  36. package/rules/no-empty-file.js +31 -2
  37. package/rules/no-exports-in-scripts.js +34 -0
  38. package/rules/no-for-loop.js +1 -5
  39. package/rules/no-hex-escape.js +1 -1
  40. package/rules/no-incorrect-query-selector.js +347 -0
  41. package/rules/no-instanceof-builtins.js +4 -6
  42. package/rules/no-invalid-fetch-options.js +1 -1
  43. package/rules/no-invalid-file-input-accept.js +475 -0
  44. package/rules/no-keyword-prefix.js +23 -5
  45. package/rules/no-late-current-target-access.js +166 -0
  46. package/rules/no-lonely-if.js +152 -52
  47. package/rules/no-manually-wrapped-comments.js +151 -0
  48. package/rules/no-null.js +16 -5
  49. package/rules/no-process-exit.js +1 -1
  50. package/rules/no-this-outside-of-class.js +75 -0
  51. package/rules/no-typeof-undefined.js +7 -4
  52. package/rules/no-unnecessary-nested-ternary.js +132 -0
  53. package/rules/no-unnecessary-polyfills.js +253 -24
  54. package/rules/no-unreadable-iife.js +33 -3
  55. package/rules/no-unused-array-method-return.js +301 -0
  56. package/rules/no-unused-properties.js +13 -19
  57. package/rules/no-useless-iterator-to-array.js +249 -0
  58. package/rules/no-useless-length-check.js +5 -11
  59. package/rules/no-useless-promise-resolve-reject.js +5 -5
  60. package/rules/no-useless-spread.js +53 -4
  61. package/rules/no-useless-undefined.js +62 -46
  62. package/rules/numeric-separators-style.js +17 -6
  63. package/rules/prefer-add-event-listener.js +1 -0
  64. package/rules/prefer-array-find.js +1 -0
  65. package/rules/prefer-array-flat.js +76 -8
  66. package/rules/prefer-array-last-methods.js +93 -0
  67. package/rules/prefer-array-some.js +74 -8
  68. package/rules/prefer-at.js +118 -57
  69. package/rules/prefer-class-fields.js +2 -4
  70. package/rules/prefer-default-parameters.js +1 -2
  71. package/rules/prefer-dom-node-append.js +2 -2
  72. package/rules/prefer-dom-node-remove.js +24 -18
  73. package/rules/prefer-export-from.js +8 -7
  74. package/rules/prefer-get-or-insert-computed.js +159 -0
  75. package/rules/prefer-global-this.js +33 -29
  76. package/rules/prefer-https.js +83 -0
  77. package/rules/prefer-includes-over-repeated-comparisons.js +199 -0
  78. package/rules/prefer-iterator-concat.js +174 -0
  79. package/rules/prefer-iterator-to-array-at-end.js +123 -0
  80. package/rules/prefer-keyboard-event-key.js +202 -76
  81. package/rules/prefer-logical-operator-over-ternary.js +1 -1
  82. package/rules/prefer-math-abs.js +397 -0
  83. package/rules/prefer-math-min-max.js +54 -26
  84. package/rules/prefer-module.js +5 -7
  85. package/rules/prefer-native-coercion-functions.js +8 -1
  86. package/rules/prefer-node-protocol.js +7 -4
  87. package/rules/prefer-number-properties.js +2 -0
  88. package/rules/prefer-object-from-entries.js +12 -7
  89. package/rules/prefer-prototype-methods.js +1 -3
  90. package/rules/prefer-query-selector.js +119 -7
  91. package/rules/prefer-queue-microtask.js +204 -0
  92. package/rules/prefer-regexp-test.js +166 -20
  93. package/rules/prefer-set-has.js +474 -47
  94. package/rules/prefer-simple-condition-first.js +282 -0
  95. package/rules/prefer-single-call.js +129 -38
  96. package/rules/prefer-split-limit.js +107 -0
  97. package/rules/prefer-spread.js +150 -19
  98. package/rules/prefer-string-match-all.js +352 -0
  99. package/rules/prefer-string-pad-start-end.js +380 -0
  100. package/rules/prefer-string-raw.js +90 -19
  101. package/rules/prefer-string-repeat.js +173 -0
  102. package/rules/prefer-string-replace-all.js +180 -12
  103. package/rules/prefer-string-slice.js +55 -4
  104. package/rules/prefer-string-starts-ends-with.js +2 -2
  105. package/rules/prefer-structured-clone.js +1 -0
  106. package/rules/prefer-switch.js +2 -0
  107. package/rules/prefer-ternary.js +3 -2
  108. package/rules/prefer-top-level-await.js +168 -6
  109. package/rules/prevent-abbreviations.js +144 -158
  110. package/rules/relative-url-style.js +1 -0
  111. package/rules/require-css-escape.js +141 -0
  112. package/rules/require-module-attributes.js +16 -18
  113. package/rules/require-module-specifiers.js +62 -0
  114. package/rules/require-passive-events.js +292 -0
  115. package/rules/rule/to-eslint-rule.js +1 -1
  116. package/rules/rule/to-eslint-rules.js +4 -6
  117. package/rules/shared/abbreviations.js +1 -0
  118. package/rules/shared/dom-events.js +7 -7
  119. package/rules/shared/identifier-checks.js +205 -0
  120. package/rules/shared/package-json.js +1 -1
  121. package/rules/string-content.js +35 -5
  122. package/rules/switch-case-braces.js +2 -3
  123. package/rules/switch-case-break-position.js +138 -0
  124. package/rules/template-indent.js +59 -3
  125. package/rules/text-encoding-identifier-case.js +23 -5
  126. package/rules/throw-new-error.js +4 -0
  127. package/rules/try-complexity.js +184 -0
  128. package/rules/utils/assert-token.js +3 -5
  129. package/rules/utils/boolean.js +37 -28
  130. package/rules/utils/create-deprecated-rules.js +25 -27
  131. package/rules/utils/get-call-expression-arguments-text.js +3 -5
  132. package/rules/utils/get-duplicate-array-elements.js +77 -0
  133. package/rules/utils/index.js +4 -1
  134. package/rules/utils/is-node-contains-lexical-this.js +65 -0
  135. package/rules/utils/is-number.js +2 -2
  136. package/rules/utils/is-same-reference.js +25 -17
  137. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +9 -9
  138. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +12 -10
  139. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +9 -5
  140. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +1 -1
  141. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +12 -8
  142. package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -1
  143. package/rules/utils/should-add-parentheses-to-unary-expression.js +10 -10
  144. package/rules/better-regex.js +0 -145
  145. package/rules/prefer-dom-node-dataset.js +0 -135
  146. package/rules/prefer-json-parse-buffer.js +0 -161
@@ -1,145 +0,0 @@
1
- import cleanRegexp from 'clean-regexp';
2
- import regexpTree from 'regexp-tree';
3
- import escapeString from './utils/escape-string.js';
4
- import {isStringLiteral, isNewExpression, isRegexLiteral} from './ast/index.js';
5
-
6
- const MESSAGE_ID = 'better-regex';
7
- const MESSAGE_ID_PARSE_ERROR = 'better-regex/parse-error';
8
- const messages = {
9
- [MESSAGE_ID]: '{{original}} can be optimized to {{optimized}}.',
10
- [MESSAGE_ID_PARSE_ERROR]: 'Problem parsing {{original}}: {{error}}',
11
- };
12
-
13
- /** @param {import('eslint').Rule.RuleContext} context */
14
- const create = context => {
15
- const {sortCharacterClasses} = context.options[0];
16
-
17
- const ignoreList = [];
18
-
19
- if (sortCharacterClasses === false) {
20
- ignoreList.push('charClassClassrangesMerge');
21
- }
22
-
23
- context.on('Literal', node => {
24
- if (!isRegexLiteral(node)) {
25
- return;
26
- }
27
-
28
- const {raw: original, regex} = node;
29
- // Regular Expressions with `u` and `v` flag are not well handled by `regexp-tree`
30
- // https://github.com/DmitrySoshnikov/regexp-tree/issues/162
31
- if (regex.flags.includes('u') || regex.flags.includes('v')) {
32
- return;
33
- }
34
-
35
- let optimized = original;
36
-
37
- try {
38
- optimized = regexpTree.optimize(original, undefined, {blacklist: ignoreList}).toString();
39
- } catch (error) {
40
- return {
41
- node,
42
- messageId: MESSAGE_ID_PARSE_ERROR,
43
- data: {
44
- original,
45
- error: error.message,
46
- },
47
- };
48
- }
49
-
50
- if (original === optimized) {
51
- return;
52
- }
53
-
54
- const problem = {
55
- node,
56
- messageId: MESSAGE_ID,
57
- data: {
58
- original,
59
- optimized,
60
- },
61
- };
62
-
63
- if (
64
- node.parent.type === 'MemberExpression'
65
- && node.parent.object === node
66
- && !node.parent.optional
67
- && !node.parent.computed
68
- && node.parent.property.type === 'Identifier'
69
- && (
70
- node.parent.property.name === 'toString'
71
- || node.parent.property.name === 'source'
72
- )
73
- ) {
74
- return problem;
75
- }
76
-
77
- return Object.assign(problem, {
78
- fix: fixer => fixer.replaceText(node, optimized),
79
- });
80
- });
81
-
82
- context.on('NewExpression', node => {
83
- if (!isNewExpression(node, {name: 'RegExp', minimumArguments: 1})) {
84
- return;
85
- }
86
-
87
- const [patternNode, flagsNode] = node.arguments;
88
-
89
- if (!isStringLiteral(patternNode)) {
90
- return;
91
- }
92
-
93
- const oldPattern = patternNode.value;
94
- const flags = isStringLiteral(flagsNode)
95
- ? flagsNode.value
96
- : '';
97
-
98
- const newPattern = cleanRegexp(oldPattern, flags);
99
-
100
- if (oldPattern !== newPattern) {
101
- return {
102
- node,
103
- messageId: MESSAGE_ID,
104
- data: {
105
- original: oldPattern,
106
- optimized: newPattern,
107
- },
108
- fix: fixer => fixer.replaceText(
109
- patternNode,
110
- escapeString(newPattern, patternNode.raw.charAt(0)),
111
- ),
112
- };
113
- }
114
- });
115
- };
116
-
117
- const schema = [
118
- {
119
- type: 'object',
120
- additionalProperties: false,
121
- properties: {
122
- sortCharacterClasses: {
123
- type: 'boolean',
124
- },
125
- },
126
- },
127
- ];
128
-
129
- /** @type {import('eslint').Rule.RuleModule} */
130
- const config = {
131
- create,
132
- meta: {
133
- type: 'suggestion',
134
- docs: {
135
- description: 'Improve regexes by making them shorter, consistent, and safer.',
136
- recommended: false,
137
- },
138
- fixable: 'code',
139
- schema,
140
- defaultOptions: [{sortCharacterClasses: true}],
141
- messages,
142
- },
143
- };
144
-
145
- export default config;
@@ -1,135 +0,0 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
- import {escapeString, hasOptionalChainElement, isValueNotUsable} from './utils/index.js';
3
- import {isMethodCall, isStringLiteral, isExpressionStatement} from './ast/index.js';
4
-
5
- const {isIdentifierName} = helperValidatorIdentifier;
6
- const MESSAGE_ID = 'prefer-dom-node-dataset';
7
- const messages = {
8
- [MESSAGE_ID]: 'Prefer `.dataset` over `{{method}}(…)`.',
9
- };
10
-
11
- const dashToCamelCase = string => string.replaceAll(/-[a-z]/g, s => s[1].toUpperCase());
12
-
13
- function getFix(callExpression, context) {
14
- const method = callExpression.callee.property.name;
15
-
16
- // `foo?.bar = ''` is invalid
17
- // TODO: Remove this restriction if https://github.com/nicolo-ribaudo/ecma262/pull/4 get merged
18
- if (method === 'setAttribute' && hasOptionalChainElement(callExpression.callee)) {
19
- return;
20
- }
21
-
22
- // `element.setAttribute(…)` returns `undefined`, but `AssignmentExpression` returns value of RHS
23
- if (method === 'setAttribute' && !isValueNotUsable(callExpression)) {
24
- return;
25
- }
26
-
27
- if (method === 'removeAttribute' && !isExpressionStatement(callExpression.parent)) {
28
- return;
29
- }
30
-
31
- return fixer => {
32
- const [nameNode] = callExpression.arguments;
33
- const name = dashToCamelCase(nameNode.value.toLowerCase().slice(5));
34
- const {sourceCode} = context;
35
- let text = '';
36
- const datasetText = `${sourceCode.getText(callExpression.callee.object)}${callExpression.callee.optional ? '?' : ''}.dataset`;
37
- switch (method) {
38
- case 'setAttribute':
39
- case 'getAttribute':
40
- case 'removeAttribute': {
41
- text = isIdentifierName(name) ? `.${name}` : `[${escapeString(name, nameNode.raw.charAt(0))}]`;
42
- text = `${datasetText}${text}`;
43
- if (method === 'setAttribute') {
44
- text += ` = ${sourceCode.getText(callExpression.arguments[1])}`;
45
- } else if (method === 'removeAttribute') {
46
- text = `delete ${text}`;
47
- }
48
-
49
- /*
50
- For non-exists attribute, `element.getAttribute('data-foo')` returns `null`,
51
- but `element.dataset.foo` returns `undefined`, switch to suggestions if necessary
52
- */
53
- break;
54
- }
55
-
56
- case 'hasAttribute': {
57
- text = `Object.hasOwn(${datasetText}, ${escapeString(name, nameNode.raw.charAt(0))})`;
58
- break;
59
- }
60
- // No default
61
- }
62
-
63
- return fixer.replaceText(callExpression, text);
64
- };
65
- }
66
-
67
- /** @param {import('eslint').Rule.RuleContext} context */
68
- const create = context => {
69
- context.on('CallExpression', callExpression => {
70
- if (!(
71
- (
72
- isMethodCall(callExpression, {
73
- method: 'setAttribute',
74
- argumentsLength: 2,
75
- optionalCall: false,
76
- optionalMember: false,
77
- })
78
- || isMethodCall(callExpression, {
79
- methods: ['removeAttribute', 'hasAttribute'],
80
- argumentsLength: 1,
81
- optionalCall: false,
82
- optionalMember: false,
83
- })
84
- || isMethodCall(callExpression, {
85
- method: 'getAttribute',
86
- argumentsLength: 1,
87
- optionalCall: false,
88
- })
89
- )
90
- && isStringLiteral(callExpression.arguments[0])
91
- )) {
92
- return;
93
- }
94
-
95
- const method = callExpression.callee.property.name;
96
- // Playwright's `Locator#getAttribute()` returns a promise.
97
- // https://playwright.dev/docs/api/class-locator#locator-get-attribute
98
- if (
99
- callExpression.parent.type === 'AwaitExpression'
100
- && callExpression.parent.argument === callExpression
101
- && method === 'getAttribute'
102
- ) {
103
- return;
104
- }
105
-
106
- const attributeName = callExpression.arguments[0].value.toLowerCase();
107
-
108
- if (!attributeName.startsWith('data-')) {
109
- return;
110
- }
111
-
112
- return {
113
- node: callExpression,
114
- messageId: MESSAGE_ID,
115
- data: {method: callExpression.callee.property.name},
116
- fix: getFix(callExpression, context),
117
- };
118
- });
119
- };
120
-
121
- /** @type {import('eslint').Rule.RuleModule} */
122
- const config = {
123
- create,
124
- meta: {
125
- type: 'suggestion',
126
- docs: {
127
- description: 'Prefer using `.dataset` on DOM elements over calling attribute methods.',
128
- recommended: 'unopinionated',
129
- },
130
- fixable: 'code',
131
- messages,
132
- },
133
- };
134
-
135
- export default config;
@@ -1,161 +0,0 @@
1
- import {findVariable, getStaticValue, getPropertyName} from '@eslint-community/eslint-utils';
2
- import {isMethodCall} from './ast/index.js';
3
- import {removeArgument} from './fix/index.js';
4
-
5
- const MESSAGE_ID = 'prefer-json-parse-buffer';
6
- const messages = {
7
- [MESSAGE_ID]: 'Prefer reading the JSON file as a buffer.',
8
- };
9
-
10
- const getAwaitExpressionArgument = node => {
11
- while (node.type === 'AwaitExpression') {
12
- node = node.argument;
13
- }
14
-
15
- return node;
16
- };
17
-
18
- function getIdentifierDeclaration(node, scope) {
19
- if (!node) {
20
- return;
21
- }
22
-
23
- node = getAwaitExpressionArgument(node);
24
-
25
- if (!node || node.type !== 'Identifier') {
26
- return node;
27
- }
28
-
29
- const variable = findVariable(scope, node);
30
- if (!variable) {
31
- return;
32
- }
33
-
34
- const {identifiers, references} = variable;
35
-
36
- if (identifiers.length !== 1 || references.length !== 2) {
37
- return;
38
- }
39
-
40
- const [identifier] = identifiers;
41
-
42
- if (
43
- identifier.parent.type !== 'VariableDeclarator'
44
- || identifier.parent.id !== identifier
45
- ) {
46
- return;
47
- }
48
-
49
- return getIdentifierDeclaration(identifier.parent.init, variable.scope);
50
- }
51
-
52
- const isUtf8EncodingStringNode = (node, scope) =>
53
- isUtf8EncodingString(getStaticValue(node, scope)?.value);
54
-
55
- const isUtf8EncodingString = value => {
56
- if (typeof value !== 'string') {
57
- return false;
58
- }
59
-
60
- value = value.toLowerCase();
61
-
62
- // eslint-disable-next-line unicorn/text-encoding-identifier-case
63
- return value === 'utf8' || value === 'utf-8';
64
- };
65
-
66
- function isUtf8Encoding(node, scope) {
67
- if (
68
- node.type === 'ObjectExpression'
69
- && node.properties.length === 1
70
- && node.properties[0].type === 'Property'
71
- && getPropertyName(node.properties[0], scope) === 'encoding'
72
- && isUtf8EncodingStringNode(node.properties[0].value, scope)
73
- ) {
74
- return true;
75
- }
76
-
77
- if (isUtf8EncodingStringNode(node, scope)) {
78
- return true;
79
- }
80
-
81
- const staticValue = getStaticValue(node, scope);
82
- if (!staticValue) {
83
- return false;
84
- }
85
-
86
- const {value} = staticValue;
87
- if (
88
- typeof value === 'object'
89
- && Object.keys(value).length === 1
90
- && isUtf8EncodingString(value.encoding)
91
- ) {
92
- return true;
93
- }
94
-
95
- return false;
96
- }
97
-
98
- /** @param {import('eslint').Rule.RuleContext} context */
99
- const create = context => {
100
- context.on('CallExpression', callExpression => {
101
- if (!(isMethodCall(callExpression, {
102
- object: 'JSON',
103
- method: 'parse',
104
- argumentsLength: 1,
105
- optionalCall: false,
106
- optionalMember: false,
107
- }))) {
108
- return;
109
- }
110
-
111
- let [node] = callExpression.arguments;
112
- const {sourceCode} = context;
113
- const scope = sourceCode.getScope(node);
114
- node = getIdentifierDeclaration(node, scope);
115
- if (
116
- !(
117
- node
118
- && node.type === 'CallExpression'
119
- && !node.optional
120
- && node.arguments.length === 2
121
- && !node.arguments.some(node => node.type === 'SpreadElement')
122
- && node.callee.type === 'MemberExpression'
123
- && !node.callee.optional
124
- )
125
- ) {
126
- return;
127
- }
128
-
129
- const method = getPropertyName(node.callee, scope);
130
- if (method !== 'readFile' && method !== 'readFileSync') {
131
- return;
132
- }
133
-
134
- const [, charsetNode] = node.arguments;
135
- if (!isUtf8Encoding(charsetNode, scope)) {
136
- return;
137
- }
138
-
139
- return {
140
- node: charsetNode,
141
- messageId: MESSAGE_ID,
142
- fix: fixer => removeArgument(fixer, charsetNode, context),
143
- };
144
- });
145
- };
146
-
147
- /** @type {import('eslint').Rule.RuleModule} */
148
- const config = {
149
- create,
150
- meta: {
151
- type: 'suggestion',
152
- docs: {
153
- description: 'Prefer reading a JSON file as a buffer.',
154
- recommended: false,
155
- },
156
- fixable: 'code',
157
- messages,
158
- },
159
- };
160
-
161
- export default config;