eslint-plugin-unicorn-ts 0.0.1-security → 50.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of eslint-plugin-unicorn-ts might be problematic. Click here for more details.

Files changed (214) hide show
  1. package/configs/all.js +6 -0
  2. package/configs/flat-config-base.js +10 -0
  3. package/configs/legacy-config-base.js +10 -0
  4. package/configs/recommended.js +117 -0
  5. package/index.js +91 -0
  6. package/license +9 -0
  7. package/package.json +186 -4
  8. package/readme.md +356 -0
  9. package/rules/ast/call-or-new-expression.js +127 -0
  10. package/rules/ast/function-types.js +5 -0
  11. package/rules/ast/index.js +39 -0
  12. package/rules/ast/is-arrow-function-body.js +7 -0
  13. package/rules/ast/is-empty-node.js +20 -0
  14. package/rules/ast/is-expression-statement.js +11 -0
  15. package/rules/ast/is-function.js +8 -0
  16. package/rules/ast/is-member-expression.js +101 -0
  17. package/rules/ast/is-method-call.js +65 -0
  18. package/rules/ast/is-reference-identifier.js +156 -0
  19. package/rules/ast/is-static-require.js +14 -0
  20. package/rules/ast/is-undefined.js +7 -0
  21. package/rules/ast/literal.js +29 -0
  22. package/rules/better-regex.js +144 -0
  23. package/rules/catch-error-name.js +136 -0
  24. package/rules/consistent-destructuring.js +168 -0
  25. package/rules/consistent-function-scoping.js +223 -0
  26. package/rules/custom-error-definition.js +215 -0
  27. package/rules/empty-brace-spaces.js +72 -0
  28. package/rules/error-message.js +104 -0
  29. package/rules/escape-case.js +63 -0
  30. package/rules/expiring-todo-comments.js +580 -0
  31. package/rules/explicit-length-check.js +229 -0
  32. package/rules/filename-case.js +258 -0
  33. package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
  34. package/rules/fix/append-argument.js +20 -0
  35. package/rules/fix/extend-fix-range.js +15 -0
  36. package/rules/fix/fix-space-around-keywords.js +35 -0
  37. package/rules/fix/index.js +23 -0
  38. package/rules/fix/remove-argument.js +32 -0
  39. package/rules/fix/remove-member-expression-property.js +11 -0
  40. package/rules/fix/remove-method-call.js +20 -0
  41. package/rules/fix/remove-parentheses.js +11 -0
  42. package/rules/fix/remove-spaces-after.js +14 -0
  43. package/rules/fix/rename-variable.js +9 -0
  44. package/rules/fix/replace-argument.js +8 -0
  45. package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
  46. package/rules/fix/replace-reference-identifier.js +35 -0
  47. package/rules/fix/replace-string-literal.js +11 -0
  48. package/rules/fix/replace-string-raw.js +14 -0
  49. package/rules/fix/replace-template-element.js +11 -0
  50. package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
  51. package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
  52. package/rules/import-style.js +364 -0
  53. package/rules/new-for-builtins.js +85 -0
  54. package/rules/no-abusive-eslint-disable.js +48 -0
  55. package/rules/no-array-callback-reference.js +256 -0
  56. package/rules/no-array-for-each.js +473 -0
  57. package/rules/no-array-method-this-argument.js +188 -0
  58. package/rules/no-array-push-push.js +144 -0
  59. package/rules/no-array-reduce.js +126 -0
  60. package/rules/no-await-expression-member.js +90 -0
  61. package/rules/no-console-spaces.js +86 -0
  62. package/rules/no-document-cookie.js +25 -0
  63. package/rules/no-empty-file.js +57 -0
  64. package/rules/no-for-loop.js +427 -0
  65. package/rules/no-hex-escape.js +46 -0
  66. package/rules/no-instanceof-array.js +65 -0
  67. package/rules/no-invalid-remove-event-listener.js +60 -0
  68. package/rules/no-keyword-prefix.js +199 -0
  69. package/rules/no-lonely-if.js +151 -0
  70. package/rules/no-negated-condition.js +144 -0
  71. package/rules/no-nested-ternary.js +58 -0
  72. package/rules/no-new-array.js +104 -0
  73. package/rules/no-new-buffer.js +98 -0
  74. package/rules/no-null.js +153 -0
  75. package/rules/no-object-as-default-parameter.js +50 -0
  76. package/rules/no-process-exit.js +104 -0
  77. package/rules/no-static-only-class.js +224 -0
  78. package/rules/no-thenable.js +198 -0
  79. package/rules/no-this-assignment.js +38 -0
  80. package/rules/no-typeof-undefined.js +143 -0
  81. package/rules/no-unnecessary-await.js +107 -0
  82. package/rules/no-unnecessary-polyfills.js +176 -0
  83. package/rules/no-unreadable-array-destructuring.js +83 -0
  84. package/rules/no-unreadable-iife.js +45 -0
  85. package/rules/no-unused-properties.js +238 -0
  86. package/rules/no-useless-fallback-in-spread.js +68 -0
  87. package/rules/no-useless-length-check.js +152 -0
  88. package/rules/no-useless-promise-resolve-reject.js +212 -0
  89. package/rules/no-useless-spread.js +381 -0
  90. package/rules/no-useless-switch-case.js +71 -0
  91. package/rules/no-useless-undefined.js +301 -0
  92. package/rules/no-zero-fractions.js +79 -0
  93. package/rules/number-literal-case.js +52 -0
  94. package/rules/numeric-separators-style.js +181 -0
  95. package/rules/prefer-add-event-listener.js +188 -0
  96. package/rules/prefer-array-find.js +423 -0
  97. package/rules/prefer-array-flat-map.js +82 -0
  98. package/rules/prefer-array-flat.js +279 -0
  99. package/rules/prefer-array-index-of.js +32 -0
  100. package/rules/prefer-array-some.js +157 -0
  101. package/rules/prefer-at.js +374 -0
  102. package/rules/prefer-blob-reading-methods.js +45 -0
  103. package/rules/prefer-code-point.js +67 -0
  104. package/rules/prefer-date-now.js +135 -0
  105. package/rules/prefer-default-parameters.js +219 -0
  106. package/rules/prefer-dom-node-append.js +48 -0
  107. package/rules/prefer-dom-node-dataset.js +120 -0
  108. package/rules/prefer-dom-node-remove.js +122 -0
  109. package/rules/prefer-dom-node-text-content.js +75 -0
  110. package/rules/prefer-event-target.js +117 -0
  111. package/rules/prefer-export-from.js +413 -0
  112. package/rules/prefer-includes.js +98 -0
  113. package/rules/prefer-json-parse-buffer.js +159 -0
  114. package/rules/prefer-keyboard-event-key.js +186 -0
  115. package/rules/prefer-logical-operator-over-ternary.js +159 -0
  116. package/rules/prefer-math-trunc.js +109 -0
  117. package/rules/prefer-modern-dom-apis.js +141 -0
  118. package/rules/prefer-modern-math-apis.js +212 -0
  119. package/rules/prefer-module.js +349 -0
  120. package/rules/prefer-native-coercion-functions.js +185 -0
  121. package/rules/prefer-negative-index.js +213 -0
  122. package/rules/prefer-node-protocol.js +61 -0
  123. package/rules/prefer-number-properties.js +126 -0
  124. package/rules/prefer-object-from-entries.js +252 -0
  125. package/rules/prefer-optional-catch-binding.js +75 -0
  126. package/rules/prefer-prototype-methods.js +88 -0
  127. package/rules/prefer-query-selector.js +135 -0
  128. package/rules/prefer-reflect-apply.js +97 -0
  129. package/rules/prefer-regexp-test.js +156 -0
  130. package/rules/prefer-set-has.js +186 -0
  131. package/rules/prefer-set-size.js +103 -0
  132. package/rules/prefer-spread.js +529 -0
  133. package/rules/prefer-string-replace-all.js +145 -0
  134. package/rules/prefer-string-slice.js +182 -0
  135. package/rules/prefer-string-starts-ends-with.js +199 -0
  136. package/rules/prefer-string-trim-start-end.js +44 -0
  137. package/rules/prefer-switch.js +344 -0
  138. package/rules/prefer-ternary.js +282 -0
  139. package/rules/prefer-top-level-await.js +152 -0
  140. package/rules/prefer-type-error.js +151 -0
  141. package/rules/prevent-abbreviations.js +645 -0
  142. package/rules/relative-url-style.js +168 -0
  143. package/rules/require-array-join-separator.js +63 -0
  144. package/rules/require-number-to-fixed-digits-argument.js +54 -0
  145. package/rules/require-post-message-target-origin.js +71 -0
  146. package/rules/shared/abbreviations.js +262 -0
  147. package/rules/shared/dom-events.js +275 -0
  148. package/rules/shared/event-keys.js +52 -0
  149. package/rules/shared/negative-index.js +46 -0
  150. package/rules/shared/simple-array-search-rule.js +128 -0
  151. package/rules/shared/typed-array.js +16 -0
  152. package/rules/string-content.js +187 -0
  153. package/rules/switch-case-braces.js +109 -0
  154. package/rules/template-indent.js +219 -0
  155. package/rules/text-encoding-identifier-case.js +108 -0
  156. package/rules/throw-new-error.js +53 -0
  157. package/rules/utils/array-or-object-prototype-property.js +63 -0
  158. package/rules/utils/assert-token.js +32 -0
  159. package/rules/utils/avoid-capture.js +146 -0
  160. package/rules/utils/boolean.js +92 -0
  161. package/rules/utils/builtins.js +36 -0
  162. package/rules/utils/cartesian-product-samples.js +24 -0
  163. package/rules/utils/create-deprecated-rules.js +25 -0
  164. package/rules/utils/escape-string.js +26 -0
  165. package/rules/utils/escape-template-element-raw.js +6 -0
  166. package/rules/utils/get-ancestor.js +20 -0
  167. package/rules/utils/get-builtin-rule.js +7 -0
  168. package/rules/utils/get-call-expression-arguments-text.js +21 -0
  169. package/rules/utils/get-class-head-location.js +22 -0
  170. package/rules/utils/get-documentation-url.js +10 -0
  171. package/rules/utils/get-indent-string.js +11 -0
  172. package/rules/utils/get-previous-node.js +24 -0
  173. package/rules/utils/get-references.js +9 -0
  174. package/rules/utils/get-scopes.js +14 -0
  175. package/rules/utils/get-switch-case-head-location.js +21 -0
  176. package/rules/utils/get-variable-identifiers.js +7 -0
  177. package/rules/utils/global-reference-tracker.js +72 -0
  178. package/rules/utils/has-optional-chain-element.js +21 -0
  179. package/rules/utils/has-same-range.js +7 -0
  180. package/rules/utils/index.js +53 -0
  181. package/rules/utils/is-function-self-used-inside.js +43 -0
  182. package/rules/utils/is-left-hand-side.js +22 -0
  183. package/rules/utils/is-logical-expression.js +16 -0
  184. package/rules/utils/is-method-named.js +9 -0
  185. package/rules/utils/is-new-expression-with-parentheses.js +26 -0
  186. package/rules/utils/is-node-matches.js +53 -0
  187. package/rules/utils/is-node-value-not-dom-node.js +21 -0
  188. package/rules/utils/is-node-value-not-function.js +42 -0
  189. package/rules/utils/is-number.js +224 -0
  190. package/rules/utils/is-object-method.js +11 -0
  191. package/rules/utils/is-on-same-line.js +7 -0
  192. package/rules/utils/is-same-identifier.js +8 -0
  193. package/rules/utils/is-same-reference.js +173 -0
  194. package/rules/utils/is-shadowed.js +33 -0
  195. package/rules/utils/is-shorthand-export-local.js +9 -0
  196. package/rules/utils/is-shorthand-import-local.js +9 -0
  197. package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
  198. package/rules/utils/is-shorthand-property-value.js +8 -0
  199. package/rules/utils/is-value-not-usable.js +5 -0
  200. package/rules/utils/lodash.js +1589 -0
  201. package/rules/utils/needs-semicolon.js +114 -0
  202. package/rules/utils/numeric.js +53 -0
  203. package/rules/utils/parentheses.js +73 -0
  204. package/rules/utils/resolve-variable-name.js +20 -0
  205. package/rules/utils/rule.js +190 -0
  206. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
  207. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
  208. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
  209. package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
  210. package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
  211. package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
  212. package/rules/utils/singular.js +18 -0
  213. package/rules/utils/to-location.js +21 -0
  214. package/README.md +0 -5
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+ const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
3
+ const {checkVueTemplate} = require('./utils/rule.js');
4
+ const isLogicalExpression = require('./utils/is-logical-expression.js');
5
+ const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean.js');
6
+ const {fixSpaceAroundKeyword} = require('./fix/index.js');
7
+ const {isLiteral, isMemberExpression, isNumberLiteral} = require('./ast/index.js');
8
+
9
+ const TYPE_NON_ZERO = 'non-zero';
10
+ const TYPE_ZERO = 'zero';
11
+ const MESSAGE_ID_SUGGESTION = 'suggestion';
12
+ const messages = {
13
+ [TYPE_NON_ZERO]: 'Use `.{{property}} {{code}}` when checking {{property}} is not zero.',
14
+ [TYPE_ZERO]: 'Use `.{{property}} {{code}}` when checking {{property}} is zero.',
15
+ [MESSAGE_ID_SUGGESTION]: 'Replace `.{{property}}` with `.{{property}} {{code}}`.',
16
+ };
17
+
18
+ const isCompareRight = (node, operator, value) =>
19
+ node.type === 'BinaryExpression'
20
+ && node.operator === operator
21
+ && isLiteral(node.right, value);
22
+ const isCompareLeft = (node, operator, value) =>
23
+ node.type === 'BinaryExpression'
24
+ && node.operator === operator
25
+ && isLiteral(node.left, value);
26
+ const nonZeroStyles = new Map([
27
+ [
28
+ 'greater-than',
29
+ {
30
+ code: '> 0',
31
+ test: node => isCompareRight(node, '>', 0),
32
+ },
33
+ ],
34
+ [
35
+ 'not-equal',
36
+ {
37
+ code: '!== 0',
38
+ test: node => isCompareRight(node, '!==', 0),
39
+ },
40
+ ],
41
+ ]);
42
+ const zeroStyle = {
43
+ code: '=== 0',
44
+ test: node => isCompareRight(node, '===', 0),
45
+ };
46
+
47
+ function getLengthCheckNode(node) {
48
+ node = node.parent;
49
+
50
+ // Zero length check
51
+ if (
52
+ // `foo.length === 0`
53
+ isCompareRight(node, '===', 0)
54
+ // `foo.length == 0`
55
+ || isCompareRight(node, '==', 0)
56
+ // `foo.length < 1`
57
+ || isCompareRight(node, '<', 1)
58
+ // `0 === foo.length`
59
+ || isCompareLeft(node, '===', 0)
60
+ // `0 == foo.length`
61
+ || isCompareLeft(node, '==', 0)
62
+ // `1 > foo.length`
63
+ || isCompareLeft(node, '>', 1)
64
+ ) {
65
+ return {isZeroLengthCheck: true, node};
66
+ }
67
+
68
+ // Non-Zero length check
69
+ if (
70
+ // `foo.length !== 0`
71
+ isCompareRight(node, '!==', 0)
72
+ // `foo.length != 0`
73
+ || isCompareRight(node, '!=', 0)
74
+ // `foo.length > 0`
75
+ || isCompareRight(node, '>', 0)
76
+ // `foo.length >= 1`
77
+ || isCompareRight(node, '>=', 1)
78
+ // `0 !== foo.length`
79
+ || isCompareLeft(node, '!==', 0)
80
+ // `0 !== foo.length`
81
+ || isCompareLeft(node, '!=', 0)
82
+ // `0 < foo.length`
83
+ || isCompareLeft(node, '<', 0)
84
+ // `1 <= foo.length`
85
+ || isCompareLeft(node, '<=', 1)
86
+ ) {
87
+ return {isZeroLengthCheck: false, node};
88
+ }
89
+
90
+ return {};
91
+ }
92
+
93
+ function isNodeValueNumber(node, context) {
94
+ if (isNumberLiteral(node)) {
95
+ return true;
96
+ }
97
+
98
+ const staticValue = getStaticValue(node, context.sourceCode.getScope(node));
99
+ return staticValue && typeof staticValue.value === 'number';
100
+ }
101
+
102
+ function create(context) {
103
+ const options = {
104
+ 'non-zero': 'greater-than',
105
+ ...context.options[0],
106
+ };
107
+ const nonZeroStyle = nonZeroStyles.get(options['non-zero']);
108
+ const {sourceCode} = context;
109
+
110
+ function getProblem({node, isZeroLengthCheck, lengthNode, autoFix}) {
111
+ const {code, test} = isZeroLengthCheck ? zeroStyle : nonZeroStyle;
112
+ if (test(node)) {
113
+ return;
114
+ }
115
+
116
+ let fixed = `${sourceCode.getText(lengthNode)} ${code}`;
117
+ if (
118
+ !isParenthesized(node, sourceCode)
119
+ && node.type === 'UnaryExpression'
120
+ && (node.parent.type === 'UnaryExpression' || node.parent.type === 'AwaitExpression')
121
+ ) {
122
+ fixed = `(${fixed})`;
123
+ }
124
+
125
+ const fix = function * (fixer) {
126
+ yield fixer.replaceText(node, fixed);
127
+ yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
128
+ };
129
+
130
+ const problem = {
131
+ node,
132
+ messageId: isZeroLengthCheck ? TYPE_ZERO : TYPE_NON_ZERO,
133
+ data: {code, property: lengthNode.property.name},
134
+ };
135
+
136
+ if (autoFix) {
137
+ problem.fix = fix;
138
+ } else {
139
+ problem.suggest = [
140
+ {
141
+ messageId: MESSAGE_ID_SUGGESTION,
142
+ fix,
143
+ },
144
+ ];
145
+ }
146
+
147
+ return problem;
148
+ }
149
+
150
+ return {
151
+ MemberExpression(memberExpression) {
152
+ if (
153
+ !isMemberExpression(memberExpression, {
154
+ properties: ['length', 'size'],
155
+ optional: false,
156
+ })
157
+ || memberExpression.object.type === 'ThisExpression'
158
+ ) {
159
+ return;
160
+ }
161
+
162
+ const lengthNode = memberExpression;
163
+ const staticValue = getStaticValue(lengthNode, sourceCode.getScope(lengthNode));
164
+ if (staticValue && (!Number.isInteger(staticValue.value) || staticValue.value < 0)) {
165
+ // Ignore known, non-positive-integer length properties.
166
+ return;
167
+ }
168
+
169
+ let node;
170
+ let autoFix = true;
171
+ let {isZeroLengthCheck, node: lengthCheckNode} = getLengthCheckNode(lengthNode);
172
+ if (lengthCheckNode) {
173
+ const {isNegative, node: ancestor} = getBooleanAncestor(lengthCheckNode);
174
+ node = ancestor;
175
+ if (isNegative) {
176
+ isZeroLengthCheck = !isZeroLengthCheck;
177
+ }
178
+ } else {
179
+ const {isNegative, node: ancestor} = getBooleanAncestor(lengthNode);
180
+ if (isBooleanNode(ancestor)) {
181
+ isZeroLengthCheck = isNegative;
182
+ node = ancestor;
183
+ } else if (
184
+ isLogicalExpression(lengthNode.parent)
185
+ && !(
186
+ lengthNode.parent.operator === '||'
187
+ && isNodeValueNumber(lengthNode.parent.right, context)
188
+ )
189
+ ) {
190
+ isZeroLengthCheck = isNegative;
191
+ node = lengthNode;
192
+ autoFix = false;
193
+ }
194
+ }
195
+
196
+ if (node) {
197
+ return getProblem({node, isZeroLengthCheck, lengthNode, autoFix});
198
+ }
199
+ },
200
+ };
201
+ }
202
+
203
+ const schema = [
204
+ {
205
+ type: 'object',
206
+ additionalProperties: false,
207
+ properties: {
208
+ 'non-zero': {
209
+ enum: [...nonZeroStyles.keys()],
210
+ default: 'greater-than',
211
+ },
212
+ },
213
+ },
214
+ ];
215
+
216
+ /** @type {import('eslint').Rule.RuleModule} */
217
+ module.exports = {
218
+ create: checkVueTemplate(create),
219
+ meta: {
220
+ type: 'problem',
221
+ docs: {
222
+ description: 'Enforce explicitly comparing the `length` or `size` property of a value.',
223
+ },
224
+ fixable: 'code',
225
+ schema,
226
+ messages,
227
+ hasSuggestions: true,
228
+ },
229
+ };
@@ -0,0 +1,258 @@
1
+ 'use strict';
2
+ const path = require('node:path');
3
+ const {camelCase, kebabCase, snakeCase, upperFirst} = require('./utils/lodash.js');
4
+ const cartesianProductSamples = require('./utils/cartesian-product-samples.js');
5
+
6
+ const MESSAGE_ID = 'filename-case';
7
+ const MESSAGE_ID_EXTENSION = 'filename-extension';
8
+ const messages = {
9
+ [MESSAGE_ID]: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.',
10
+ [MESSAGE_ID_EXTENSION]: 'File extension `{{extension}}` is not in lowercase. Rename it to `{{filename}}`.',
11
+ };
12
+
13
+ const pascalCase = string => upperFirst(camelCase(string));
14
+ const numberRegex = /\d+/;
15
+ const PLACEHOLDER = '\uFFFF\uFFFF\uFFFF';
16
+ const PLACEHOLDER_REGEX = new RegExp(PLACEHOLDER, 'i');
17
+ const isIgnoredChar = char => !/^[a-z\d-_]$/i.test(char);
18
+ const ignoredByDefault = new Set(['index.js', 'index.mjs', 'index.cjs', 'index.ts', 'index.tsx', 'index.vue']);
19
+ const isLowerCase = string => string === string.toLowerCase();
20
+
21
+ function ignoreNumbers(caseFunction) {
22
+ return string => {
23
+ const stack = [];
24
+ let execResult = numberRegex.exec(string);
25
+
26
+ while (execResult) {
27
+ stack.push(execResult[0]);
28
+ string = string.replace(execResult[0], PLACEHOLDER);
29
+ execResult = numberRegex.exec(string);
30
+ }
31
+
32
+ let withCase = caseFunction(string);
33
+
34
+ while (stack.length > 0) {
35
+ withCase = withCase.replace(PLACEHOLDER_REGEX, stack.shift());
36
+ }
37
+
38
+ return withCase;
39
+ };
40
+ }
41
+
42
+ const cases = {
43
+ camelCase: {
44
+ fn: camelCase,
45
+ name: 'camel case',
46
+ },
47
+ kebabCase: {
48
+ fn: kebabCase,
49
+ name: 'kebab case',
50
+ },
51
+ snakeCase: {
52
+ fn: snakeCase,
53
+ name: 'snake case',
54
+ },
55
+ pascalCase: {
56
+ fn: pascalCase,
57
+ name: 'pascal case',
58
+ },
59
+ };
60
+
61
+ /**
62
+ Get the cases specified by the option.
63
+
64
+ @param {object} options
65
+ @returns {string[]} The chosen cases.
66
+ */
67
+ function getChosenCases(options) {
68
+ if (options.case) {
69
+ return [options.case];
70
+ }
71
+
72
+ if (options.cases) {
73
+ const cases = Object.keys(options.cases)
74
+ .filter(cases => options.cases[cases]);
75
+
76
+ return cases.length > 0 ? cases : ['kebabCase'];
77
+ }
78
+
79
+ return ['kebabCase'];
80
+ }
81
+
82
+ function validateFilename(words, caseFunctions) {
83
+ return words
84
+ .filter(({ignored}) => !ignored)
85
+ .every(({word}) => caseFunctions.some(caseFunction => caseFunction(word) === word));
86
+ }
87
+
88
+ function fixFilename(words, caseFunctions, {leading, extension}) {
89
+ const replacements = words
90
+ .map(({word, ignored}) => ignored ? [word] : caseFunctions.map(caseFunction => caseFunction(word)));
91
+
92
+ const {
93
+ samples: combinations,
94
+ } = cartesianProductSamples(replacements);
95
+
96
+ return [...new Set(combinations.map(parts => `${leading}${parts.join('')}${extension.toLowerCase()}`))];
97
+ }
98
+
99
+ const leadingUnderscoresRegex = /^(?<leading>_+)(?<tailing>.*)$/;
100
+ function splitFilename(filename) {
101
+ const result = leadingUnderscoresRegex.exec(filename) || {groups: {}};
102
+ const {leading = '', tailing = filename} = result.groups;
103
+
104
+ const words = [];
105
+
106
+ let lastWord;
107
+ for (const char of tailing) {
108
+ const isIgnored = isIgnoredChar(char);
109
+
110
+ if (lastWord?.ignored === isIgnored) {
111
+ lastWord.word += char;
112
+ } else {
113
+ lastWord = {
114
+ word: char,
115
+ ignored: isIgnored,
116
+ };
117
+ words.push(lastWord);
118
+ }
119
+ }
120
+
121
+ return {
122
+ leading,
123
+ words,
124
+ };
125
+ }
126
+
127
+ /**
128
+ Turns `[a, b, c]` into `a, b, or c`.
129
+
130
+ @param {string[]} words
131
+ @returns {string}
132
+ */
133
+ const englishishJoinWords = words => new Intl.ListFormat('en-US', {type: 'disjunction'}).format(words);
134
+
135
+ /** @param {import('eslint').Rule.RuleContext} context */
136
+ const create = context => {
137
+ const options = context.options[0] || {};
138
+ const chosenCases = getChosenCases(options);
139
+ const ignore = (options.ignore || []).map(item => {
140
+ if (item instanceof RegExp) {
141
+ return item;
142
+ }
143
+
144
+ return new RegExp(item, 'u');
145
+ });
146
+ const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
147
+ const filenameWithExtension = context.physicalFilename;
148
+
149
+ if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {
150
+ return;
151
+ }
152
+
153
+ return {
154
+ Program() {
155
+ const extension = path.extname(filenameWithExtension);
156
+ const filename = path.basename(filenameWithExtension, extension);
157
+ const base = filename + extension;
158
+
159
+ if (ignoredByDefault.has(base) || ignore.some(regexp => regexp.test(base))) {
160
+ return;
161
+ }
162
+
163
+ const {leading, words} = splitFilename(filename);
164
+ const isValid = validateFilename(words, chosenCasesFunctions);
165
+
166
+ if (isValid) {
167
+ if (!isLowerCase(extension)) {
168
+ return {
169
+ loc: {column: 0, line: 1},
170
+ messageId: MESSAGE_ID_EXTENSION,
171
+ data: {filename: filename + extension.toLowerCase(), extension},
172
+ };
173
+ }
174
+
175
+ return;
176
+ }
177
+
178
+ const renamedFilenames = fixFilename(words, chosenCasesFunctions, {
179
+ leading,
180
+ extension,
181
+ });
182
+
183
+ return {
184
+ // Report on first character like `unicode-bom` rule
185
+ // https://github.com/eslint/eslint/blob/8a77b661bc921c3408bae01b3aa41579edfc6e58/lib/rules/unicode-bom.js#L46
186
+ loc: {column: 0, line: 1},
187
+ messageId: MESSAGE_ID,
188
+ data: {
189
+ chosenCases: englishishJoinWords(chosenCases.map(x => cases[x].name)),
190
+ renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``)),
191
+ },
192
+ };
193
+ },
194
+ };
195
+ };
196
+
197
+ const schema = [
198
+ {
199
+ oneOf: [
200
+ {
201
+ properties: {
202
+ case: {
203
+ enum: [
204
+ 'camelCase',
205
+ 'snakeCase',
206
+ 'kebabCase',
207
+ 'pascalCase',
208
+ ],
209
+ },
210
+ ignore: {
211
+ type: 'array',
212
+ uniqueItems: true,
213
+ },
214
+ },
215
+ additionalProperties: false,
216
+ },
217
+ {
218
+ properties: {
219
+ cases: {
220
+ properties: {
221
+ camelCase: {
222
+ type: 'boolean',
223
+ },
224
+ snakeCase: {
225
+ type: 'boolean',
226
+ },
227
+ kebabCase: {
228
+ type: 'boolean',
229
+ },
230
+ pascalCase: {
231
+ type: 'boolean',
232
+ },
233
+ },
234
+ additionalProperties: false,
235
+ },
236
+ ignore: {
237
+ type: 'array',
238
+ uniqueItems: true,
239
+ },
240
+ },
241
+ additionalProperties: false,
242
+ },
243
+ ],
244
+ },
245
+ ];
246
+
247
+ /** @type {import('eslint').Rule.RuleModule} */
248
+ module.exports = {
249
+ create,
250
+ meta: {
251
+ type: 'suggestion',
252
+ docs: {
253
+ description: 'Enforce a case style for filenames.',
254
+ },
255
+ schema,
256
+ messages,
257
+ },
258
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+ const {isSemicolonToken} = require('@eslint-community/eslint-utils');
3
+
4
+ function * addParenthesizesToReturnOrThrowExpression(fixer, node, sourceCode) {
5
+ if (node.type !== 'ReturnStatement' && node.type !== 'ThrowStatement') {
6
+ return;
7
+ }
8
+
9
+ const returnOrThrowToken = sourceCode.getFirstToken(node);
10
+ yield fixer.insertTextAfter(returnOrThrowToken, ' (');
11
+
12
+ const lastToken = sourceCode.getLastToken(node);
13
+ if (!isSemicolonToken(lastToken)) {
14
+ yield fixer.insertTextAfter(node, ')');
15
+ return;
16
+ }
17
+
18
+ yield fixer.insertTextBefore(lastToken, ')');
19
+ }
20
+
21
+ module.exports = addParenthesizesToReturnOrThrowExpression;
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+ const {isCommaToken} = require('@eslint-community/eslint-utils');
3
+
4
+ function appendArgument(fixer, node, text, sourceCode) {
5
+ // This function should also work for `NewExpression`
6
+ // But parentheses of `NewExpression` could be omitted, add this check to prevent accident use on it
7
+ /* c8 ignore next 3 */
8
+ if (node.type !== 'CallExpression') {
9
+ throw new Error(`Unexpected node "${node.type}".`);
10
+ }
11
+
12
+ const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
13
+ if (node.arguments.length > 0) {
14
+ text = isCommaToken(penultimateToken) ? ` ${text},` : `, ${text}`;
15
+ }
16
+
17
+ return fixer.insertTextBefore(lastToken, text);
18
+ }
19
+
20
+ module.exports = appendArgument;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ Extend fix range to prevent changes from other rules.
5
+ https://github.com/eslint/eslint/pull/13748/files#diff-c692f3fde09eda7c89f1802c908511a3fb59f5d207fe95eb009cb52e46a99e84R348
6
+
7
+ @param {ruleFixer} fixer - The fixer to fix.
8
+ @param {int[]} range - The extended range node.
9
+ */
10
+ function * extendFixRange(fixer, range) {
11
+ yield fixer.insertTextBeforeRange(range, '');
12
+ yield fixer.insertTextAfterRange(range, '');
13
+ }
14
+
15
+ module.exports = extendFixRange;
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+ const {getParenthesizedRange} = require('../utils/parentheses.js');
3
+
4
+ const isProblematicToken = ({type, value}) => (
5
+ (type === 'Keyword' && /^[a-z]*$/.test(value))
6
+ // ForOfStatement
7
+ || (type === 'Identifier' && value === 'of')
8
+ // AwaitExpression
9
+ || (type === 'Identifier' && value === 'await')
10
+ );
11
+
12
+ function * fixSpaceAroundKeyword(fixer, node, sourceCode) {
13
+ const range = getParenthesizedRange(node, sourceCode);
14
+ const tokenBefore = sourceCode.getTokenBefore({range}, {includeComments: true});
15
+
16
+ if (
17
+ tokenBefore
18
+ && range[0] === tokenBefore.range[1]
19
+ && isProblematicToken(tokenBefore)
20
+ ) {
21
+ yield fixer.insertTextAfter(tokenBefore, ' ');
22
+ }
23
+
24
+ const tokenAfter = sourceCode.getTokenAfter({range}, {includeComments: true});
25
+
26
+ if (
27
+ tokenAfter
28
+ && range[1] === tokenAfter.range[0]
29
+ && isProblematicToken(tokenAfter)
30
+ ) {
31
+ yield fixer.insertTextBefore(tokenAfter, ' ');
32
+ }
33
+ }
34
+
35
+ module.exports = fixSpaceAroundKeyword;
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ // Utilities
5
+ extendFixRange: require('./extend-fix-range.js'),
6
+ removeParentheses: require('./remove-parentheses.js'),
7
+
8
+ appendArgument: require('./append-argument.js'),
9
+ removeArgument: require('./remove-argument.js'),
10
+ replaceArgument: require('./replace-argument.js'),
11
+ switchNewExpressionToCallExpression: require('./switch-new-expression-to-call-expression.js'),
12
+ switchCallExpressionToNewExpression: require('./switch-call-expression-to-new-expression.js'),
13
+ removeMemberExpressionProperty: require('./remove-member-expression-property.js'),
14
+ removeMethodCall: require('./remove-method-call.js'),
15
+ replaceTemplateElement: require('./replace-template-element.js'),
16
+ replaceReferenceIdentifier: require('./replace-reference-identifier.js'),
17
+ renameVariable: require('./rename-variable.js'),
18
+ replaceNodeOrTokenAndSpacesBefore: require('./replace-node-or-token-and-spaces-before.js'),
19
+ removeSpacesAfter: require('./remove-spaces-after.js'),
20
+ fixSpaceAroundKeyword: require('./fix-space-around-keywords.js'),
21
+ replaceStringLiteral: require('./replace-string-literal.js'),
22
+ addParenthesizesToReturnOrThrowExpression: require('./add-parenthesizes-to-return-or-throw-expression.js'),
23
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+ const {isCommaToken} = require('@eslint-community/eslint-utils');
3
+ const {getParentheses} = require('../utils/parentheses.js');
4
+
5
+ function removeArgument(fixer, node, sourceCode) {
6
+ const callExpression = node.parent;
7
+ const index = callExpression.arguments.indexOf(node);
8
+ const parentheses = getParentheses(node, sourceCode);
9
+ const firstToken = parentheses[0] || node;
10
+ const lastToken = parentheses.at(-1) || node;
11
+
12
+ let [start] = firstToken.range;
13
+ let [, end] = lastToken.range;
14
+
15
+ if (index !== 0) {
16
+ start = sourceCode.getTokenBefore(firstToken).range[0];
17
+ }
18
+
19
+ // If the removed argument is the only argument, the trailing comma must be removed too
20
+ /* c8 ignore start */
21
+ if (callExpression.arguments.length === 1) {
22
+ const tokenAfter = sourceCode.getTokenBefore(lastToken);
23
+ if (isCommaToken(tokenAfter)) {
24
+ end = tokenAfter[1];
25
+ }
26
+ }
27
+ /* c8 ignore end */
28
+
29
+ return fixer.replaceTextRange([start, end], '');
30
+ }
31
+
32
+ module.exports = removeArgument;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+ const {getParenthesizedRange} = require('../utils/parentheses.js');
3
+
4
+ function removeMemberExpressionProperty(fixer, memberExpression, sourceCode) {
5
+ const [, start] = getParenthesizedRange(memberExpression.object, sourceCode);
6
+ const [, end] = memberExpression.range;
7
+
8
+ return fixer.removeRange([start, end]);
9
+ }
10
+
11
+ module.exports = removeMemberExpressionProperty;
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+ const {getParenthesizedRange} = require('../utils/parentheses.js');
3
+ const removeMemberExpressionProperty = require('./remove-member-expression-property.js');
4
+
5
+ function * removeMethodCall(fixer, callExpression, sourceCode) {
6
+ const memberExpression = callExpression.callee;
7
+
8
+ // `(( (( foo )).bar ))()`
9
+ // ^^^^
10
+ yield removeMemberExpressionProperty(fixer, memberExpression, sourceCode);
11
+
12
+ // `(( (( foo )).bar ))()`
13
+ // ^^
14
+ const [, start] = getParenthesizedRange(memberExpression, sourceCode);
15
+ const [, end] = callExpression.range;
16
+
17
+ yield fixer.removeRange([start, end]);
18
+ }
19
+
20
+ module.exports = removeMethodCall;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+ const {getParentheses} = require('../utils/parentheses.js');
3
+
4
+ function * removeParentheses(node, fixer, sourceCode) {
5
+ const parentheses = getParentheses(node, sourceCode);
6
+ for (const token of parentheses) {
7
+ yield fixer.remove(token);
8
+ }
9
+ }
10
+
11
+ module.exports = removeParentheses;
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ function removeSpacesAfter(indexOrNodeOrToken, sourceCode, fixer) {
4
+ let index = indexOrNodeOrToken;
5
+ if (typeof indexOrNodeOrToken === 'object' && Array.isArray(indexOrNodeOrToken.range)) {
6
+ index = indexOrNodeOrToken.range[1];
7
+ }
8
+
9
+ const textAfter = sourceCode.text.slice(index);
10
+ const [leadingSpaces] = textAfter.match(/^\s*/);
11
+ return fixer.removeRange([index, index + leadingSpaces.length]);
12
+ }
13
+
14
+ module.exports = removeSpacesAfter;