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,182 @@
1
+ 'use strict';
2
+ const {getStaticValue} = require('@eslint-community/eslint-utils');
3
+ const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
4
+ const isNumber = require('./utils/is-number.js');
5
+ const {replaceArgument} = require('./fix/index.js');
6
+ const {isNumberLiteral, isMethodCall} = require('./ast/index.js');
7
+
8
+ const MESSAGE_ID_SUBSTR = 'substr';
9
+ const MESSAGE_ID_SUBSTRING = 'substring';
10
+ const messages = {
11
+ [MESSAGE_ID_SUBSTR]: 'Prefer `String#slice()` over `String#substr()`.',
12
+ [MESSAGE_ID_SUBSTRING]: 'Prefer `String#slice()` over `String#substring()`.',
13
+ };
14
+
15
+ const getNumericValue = node => {
16
+ if (isNumberLiteral(node)) {
17
+ return node.value;
18
+ }
19
+
20
+ if (node.type === 'UnaryExpression' && node.operator === '-') {
21
+ return -getNumericValue(node.argument);
22
+ }
23
+ };
24
+
25
+ // This handles cases where the argument is very likely to be a number, such as `.substring('foo'.length)`.
26
+ const isLengthProperty = node => (
27
+ node?.type === 'MemberExpression'
28
+ && node.computed === false
29
+ && node.property.type === 'Identifier'
30
+ && node.property.name === 'length'
31
+ );
32
+
33
+ function * fixSubstrArguments({node, fixer, context, abort}) {
34
+ const argumentNodes = node.arguments;
35
+ const [firstArgument, secondArgument] = argumentNodes;
36
+
37
+ if (!secondArgument) {
38
+ return;
39
+ }
40
+
41
+ const {sourceCode} = context;
42
+ const scope = sourceCode.getScope(node);
43
+ const firstArgumentStaticResult = getStaticValue(firstArgument, scope);
44
+ const secondArgumentRange = getParenthesizedRange(secondArgument, sourceCode);
45
+ const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
46
+
47
+ if (firstArgumentStaticResult?.value === 0) {
48
+ if (isNumberLiteral(secondArgument) || isLengthProperty(secondArgument)) {
49
+ return;
50
+ }
51
+
52
+ if (typeof getNumericValue(secondArgument) === 'number') {
53
+ yield replaceSecondArgument(Math.max(0, getNumericValue(secondArgument)));
54
+ return;
55
+ }
56
+
57
+ yield fixer.insertTextBeforeRange(secondArgumentRange, 'Math.max(0, ');
58
+ yield fixer.insertTextAfterRange(secondArgumentRange, ')');
59
+ return;
60
+ }
61
+
62
+ if (argumentNodes.every(node => isNumberLiteral(node))) {
63
+ yield replaceSecondArgument(firstArgument.value + secondArgument.value);
64
+ return;
65
+ }
66
+
67
+ if (argumentNodes.every(node => isNumber(node, scope))) {
68
+ const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
69
+
70
+ yield fixer.insertTextBeforeRange(secondArgumentRange, `${firstArgumentText} + `);
71
+ return;
72
+ }
73
+
74
+ return abort();
75
+ }
76
+
77
+ function * fixSubstringArguments({node, fixer, context, abort}) {
78
+ const {sourceCode} = context;
79
+ const [firstArgument, secondArgument] = node.arguments;
80
+
81
+ const firstNumber = firstArgument ? getNumericValue(firstArgument) : undefined;
82
+ const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
83
+ const replaceFirstArgument = text => replaceArgument(fixer, firstArgument, text, sourceCode);
84
+
85
+ if (!secondArgument) {
86
+ if (isLengthProperty(firstArgument)) {
87
+ return;
88
+ }
89
+
90
+ if (firstNumber !== undefined) {
91
+ yield replaceFirstArgument(Math.max(0, firstNumber));
92
+ return;
93
+ }
94
+
95
+ const firstArgumentRange = getParenthesizedRange(firstArgument, sourceCode);
96
+ yield fixer.insertTextBeforeRange(firstArgumentRange, 'Math.max(0, ');
97
+ yield fixer.insertTextAfterRange(firstArgumentRange, ')');
98
+ return;
99
+ }
100
+
101
+ const secondNumber = getNumericValue(secondArgument);
102
+ const secondArgumentText = getParenthesizedText(secondArgument, sourceCode);
103
+ const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
104
+
105
+ if (firstNumber !== undefined && secondNumber !== undefined) {
106
+ const argumentsValue = [Math.max(0, firstNumber), Math.max(0, secondNumber)];
107
+ if (firstNumber > secondNumber) {
108
+ argumentsValue.reverse();
109
+ }
110
+
111
+ if (argumentsValue[0] !== firstNumber) {
112
+ yield replaceFirstArgument(argumentsValue[0]);
113
+ }
114
+
115
+ if (argumentsValue[1] !== secondNumber) {
116
+ yield replaceSecondArgument(argumentsValue[1]);
117
+ }
118
+
119
+ return;
120
+ }
121
+
122
+ if (firstNumber === 0 || secondNumber === 0) {
123
+ yield replaceFirstArgument(0);
124
+ yield replaceSecondArgument(`Math.max(0, ${firstNumber === 0 ? secondArgumentText : firstArgumentText})`);
125
+ return;
126
+ }
127
+
128
+ // As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
129
+ // .substring(0, 2) and .substring(2, 0) returns the same result
130
+ // .slice(0, 2) and .slice(2, 0) doesn't return the same result
131
+ // There's also an issue with us now knowing whether the value will be negative or not, due to:
132
+ // .substring() treats a negative number the same as it treats a zero.
133
+ // The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
134
+
135
+ return abort();
136
+ }
137
+
138
+ /** @param {import('eslint').Rule.RuleContext} context */
139
+ const create = context => ({
140
+ CallExpression(node) {
141
+ if (!isMethodCall(node, {methods: ['substr', 'substring']})) {
142
+ return;
143
+ }
144
+
145
+ const method = node.callee.property.name;
146
+
147
+ return {
148
+ node,
149
+ messageId: method,
150
+ * fix(fixer, {abort}) {
151
+ yield fixer.replaceText(node.callee.property, 'slice');
152
+
153
+ if (node.arguments.length === 0) {
154
+ return;
155
+ }
156
+
157
+ if (
158
+ node.arguments.length > 2
159
+ || node.arguments.some(node => node.type === 'SpreadElement')
160
+ ) {
161
+ return abort();
162
+ }
163
+
164
+ const fixArguments = method === 'substr' ? fixSubstrArguments : fixSubstringArguments;
165
+ yield * fixArguments({node, fixer, context, abort});
166
+ },
167
+ };
168
+ },
169
+ });
170
+
171
+ /** @type {import('eslint').Rule.RuleModule} */
172
+ module.exports = {
173
+ create,
174
+ meta: {
175
+ type: 'suggestion',
176
+ docs: {
177
+ description: 'Prefer `String#slice()` over `String#substr()` and `String#substring()`.',
178
+ },
179
+ fixable: 'code',
180
+ messages,
181
+ },
182
+ };
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+ const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
3
+ const escapeString = require('./utils/escape-string.js');
4
+ const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
5
+ const shouldAddParenthesesToLogicalExpressionChild = require('./utils/should-add-parentheses-to-logical-expression-child.js');
6
+ const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
7
+ const {isMethodCall, isRegexLiteral} = require('./ast/index.js');
8
+
9
+ const MESSAGE_STARTS_WITH = 'prefer-starts-with';
10
+ const MESSAGE_ENDS_WITH = 'prefer-ends-with';
11
+ const FIX_TYPE_STRING_CASTING = 'useStringCasting';
12
+ const FIX_TYPE_OPTIONAL_CHAINING = 'useOptionalChaining';
13
+ const FIX_TYPE_NULLISH_COALESCING = 'useNullishCoalescing';
14
+ const messages = {
15
+ [MESSAGE_STARTS_WITH]: 'Prefer `String#startsWith()` over a regex with `^`.',
16
+ [MESSAGE_ENDS_WITH]: 'Prefer `String#endsWith()` over a regex with `$`.',
17
+ [FIX_TYPE_STRING_CASTING]: 'Convert to string `String(…).{{method}}()`.',
18
+ [FIX_TYPE_OPTIONAL_CHAINING]: 'Use optional chaining `…?.{{method}}()`.',
19
+ [FIX_TYPE_NULLISH_COALESCING]: 'Use nullish coalescing `(… ?? \'\').{{method}}()`.',
20
+ };
21
+
22
+ const doesNotContain = (string, characters) => characters.every(character => !string.includes(character));
23
+ const isSimpleString = string => doesNotContain(
24
+ string,
25
+ ['^', '$', '+', '[', '{', '(', '\\', '.', '?', '*', '|'],
26
+ );
27
+ const addParentheses = text => `(${text})`;
28
+
29
+ const checkRegex = ({pattern, flags}) => {
30
+ if (flags.includes('i') || flags.includes('m')) {
31
+ return;
32
+ }
33
+
34
+ if (pattern.startsWith('^')) {
35
+ const string = pattern.slice(1);
36
+
37
+ if (isSimpleString(string)) {
38
+ return {
39
+ messageId: MESSAGE_STARTS_WITH,
40
+ string,
41
+ };
42
+ }
43
+ }
44
+
45
+ if (pattern.endsWith('$')) {
46
+ const string = pattern.slice(0, -1);
47
+
48
+ if (isSimpleString(string)) {
49
+ return {
50
+ messageId: MESSAGE_ENDS_WITH,
51
+ string,
52
+ };
53
+ }
54
+ }
55
+ };
56
+
57
+ /** @param {import('eslint').Rule.RuleContext} context */
58
+ const create = context => {
59
+ const {sourceCode} = context;
60
+
61
+ return {
62
+ CallExpression(node) {
63
+ if (
64
+ !isMethodCall(node, {
65
+ method: 'test',
66
+ argumentsLength: 1,
67
+ optionalCall: false,
68
+ optionalMember: false,
69
+ })
70
+ || !isRegexLiteral(node.callee.object)
71
+ ) {
72
+ return;
73
+ }
74
+
75
+ const regexNode = node.callee.object;
76
+ const {regex} = regexNode;
77
+ const result = checkRegex(regex);
78
+ if (!result) {
79
+ return;
80
+ }
81
+
82
+ const [target] = node.arguments;
83
+ const method = result.messageId === MESSAGE_STARTS_WITH ? 'startsWith' : 'endsWith';
84
+
85
+ let isString = target.type === 'TemplateLiteral'
86
+ || (
87
+ target.type === 'CallExpression'
88
+ && target.callee.type === 'Identifier'
89
+ && target.callee.name === 'String'
90
+ );
91
+ let isNonString = false;
92
+ if (!isString) {
93
+ const staticValue = getStaticValue(target, sourceCode.getScope(target));
94
+
95
+ if (staticValue) {
96
+ isString = typeof staticValue.value === 'string';
97
+ isNonString = !isString;
98
+ }
99
+ }
100
+
101
+ const problem = {
102
+ node,
103
+ messageId: result.messageId,
104
+ };
105
+
106
+ function * fix(fixer, fixType) {
107
+ let targetText = getParenthesizedText(target, sourceCode);
108
+ const isRegexParenthesized = isParenthesized(regexNode, sourceCode);
109
+ const isTargetParenthesized = isParenthesized(target, sourceCode);
110
+
111
+ switch (fixType) {
112
+ // Goal: `(target ?? '').startsWith(pattern)`
113
+ case FIX_TYPE_NULLISH_COALESCING: {
114
+ if (
115
+ !isTargetParenthesized
116
+ && shouldAddParenthesesToLogicalExpressionChild(target, {operator: '??', property: 'left'})
117
+ ) {
118
+ targetText = addParentheses(targetText);
119
+ }
120
+
121
+ targetText += ' ?? \'\'';
122
+
123
+ // `LogicalExpression` need add parentheses to call `.startsWith()`,
124
+ // but if regex is parenthesized, we can reuse it
125
+ if (!isRegexParenthesized) {
126
+ targetText = addParentheses(targetText);
127
+ }
128
+
129
+ break;
130
+ }
131
+
132
+ // Goal: `String(target).startsWith(pattern)`
133
+ case FIX_TYPE_STRING_CASTING: {
134
+ // `target` was a call argument, don't need check parentheses
135
+ targetText = `String(${targetText})`;
136
+ // `CallExpression` don't need add parentheses to call `.startsWith()`
137
+ break;
138
+ }
139
+
140
+ // Goal: `target.startsWith(pattern)` or `target?.startsWith(pattern)`
141
+ case FIX_TYPE_OPTIONAL_CHAINING: {
142
+ // Optional chaining: `target.startsWith` => `target?.startsWith`
143
+ yield fixer.replaceText(sourceCode.getTokenBefore(node.callee.property), '?.');
144
+ }
145
+
146
+ // Fallthrough
147
+ default: {
148
+ if (
149
+ !isRegexParenthesized
150
+ && !isTargetParenthesized
151
+ && shouldAddParenthesesToMemberExpressionObject(target, sourceCode)
152
+ ) {
153
+ targetText = addParentheses(targetText);
154
+ }
155
+ }
156
+ }
157
+
158
+ // The regex literal always starts with `/` or `(`, so we don't need check ASI
159
+
160
+ // Replace regex with string
161
+ yield fixer.replaceText(regexNode, targetText);
162
+
163
+ // `.test` => `.startsWith` / `.endsWith`
164
+ yield fixer.replaceText(node.callee.property, method);
165
+
166
+ // Replace argument with result.string
167
+ yield fixer.replaceTextRange(getParenthesizedRange(target, sourceCode), escapeString(result.string));
168
+ }
169
+
170
+ if (isString || !isNonString) {
171
+ problem.fix = fix;
172
+ }
173
+
174
+ if (!isString) {
175
+ problem.suggest = [
176
+ FIX_TYPE_STRING_CASTING,
177
+ FIX_TYPE_OPTIONAL_CHAINING,
178
+ FIX_TYPE_NULLISH_COALESCING,
179
+ ].map(type => ({messageId: type, data: {method}, fix: fixer => fix(fixer, type)}));
180
+ }
181
+
182
+ return problem;
183
+ },
184
+ };
185
+ };
186
+
187
+ /** @type {import('eslint').Rule.RuleModule} */
188
+ module.exports = {
189
+ create,
190
+ meta: {
191
+ type: 'suggestion',
192
+ docs: {
193
+ description: 'Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`.',
194
+ },
195
+ fixable: 'code',
196
+ hasSuggestions: true,
197
+ messages,
198
+ },
199
+ };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+ const {isMethodCall} = require('./ast/index.js');
3
+
4
+ const MESSAGE_ID = 'prefer-string-trim-start-end';
5
+ const messages = {
6
+ [MESSAGE_ID]: 'Prefer `String#{{replacement}}()` over `String#{{method}}()`.',
7
+ };
8
+
9
+ /** @param {import('eslint').Rule.RuleContext} context */
10
+ const create = () => ({
11
+ CallExpression(callExpression) {
12
+ if (!isMethodCall(callExpression, {
13
+ methods: ['trimLeft', 'trimRight'],
14
+ argumentsLength: 0,
15
+ optionalCall: false,
16
+ })) {
17
+ return;
18
+ }
19
+
20
+ const node = callExpression.callee.property;
21
+ const method = node.name;
22
+ const replacement = method === 'trimLeft' ? 'trimStart' : 'trimEnd';
23
+
24
+ return {
25
+ node,
26
+ messageId: MESSAGE_ID,
27
+ data: {method, replacement},
28
+ fix: fixer => fixer.replaceText(node, replacement),
29
+ };
30
+ },
31
+ });
32
+
33
+ /** @type {import('eslint').Rule.RuleModule} */
34
+ module.exports = {
35
+ create,
36
+ meta: {
37
+ type: 'suggestion',
38
+ docs: {
39
+ description: 'Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`.',
40
+ },
41
+ fixable: 'code',
42
+ messages,
43
+ },
44
+ };