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,580 @@
1
+ 'use strict';
2
+ const path = require('node:path');
3
+ const readPkgUp = require('read-pkg-up');
4
+ const semver = require('semver');
5
+ const ci = require('ci-info');
6
+ const getBuiltinRule = require('./utils/get-builtin-rule.js');
7
+
8
+ const baseRule = getBuiltinRule('no-warning-comments');
9
+
10
+ // `unicorn/` prefix is added to avoid conflicts with core rule
11
+ const MESSAGE_ID_AVOID_MULTIPLE_DATES = 'unicorn/avoidMultipleDates';
12
+ const MESSAGE_ID_EXPIRED_TODO = 'unicorn/expiredTodo';
13
+ const MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS
14
+ = 'unicorn/avoidMultiplePackageVersions';
15
+ const MESSAGE_ID_REACHED_PACKAGE_VERSION = 'unicorn/reachedPackageVersion';
16
+ const MESSAGE_ID_HAVE_PACKAGE = 'unicorn/havePackage';
17
+ const MESSAGE_ID_DONT_HAVE_PACKAGE = 'unicorn/dontHavePackage';
18
+ const MESSAGE_ID_VERSION_MATCHES = 'unicorn/versionMatches';
19
+ const MESSAGE_ID_ENGINE_MATCHES = 'unicorn/engineMatches';
20
+ const MESSAGE_ID_REMOVE_WHITESPACE = 'unicorn/removeWhitespaces';
21
+ const MESSAGE_ID_MISSING_AT_SYMBOL = 'unicorn/missingAtSymbol';
22
+
23
+ // Override of core rule message with a more specific one - no prefix
24
+ const MESSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT = 'unexpectedComment';
25
+ const messages = {
26
+ [MESSAGE_ID_AVOID_MULTIPLE_DATES]:
27
+ 'Avoid using multiple expiration dates in TODO: {{expirationDates}}. {{message}}',
28
+ [MESSAGE_ID_EXPIRED_TODO]:
29
+ 'There is a TODO that is past due date: {{expirationDate}}. {{message}}',
30
+ [MESSAGE_ID_REACHED_PACKAGE_VERSION]:
31
+ 'There is a TODO that is past due package version: {{comparison}}. {{message}}',
32
+ [MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS]:
33
+ 'Avoid using multiple package versions in TODO: {{versions}}. {{message}}',
34
+ [MESSAGE_ID_HAVE_PACKAGE]:
35
+ 'There is a TODO that is deprecated since you installed: {{package}}. {{message}}',
36
+ [MESSAGE_ID_DONT_HAVE_PACKAGE]:
37
+ 'There is a TODO that is deprecated since you uninstalled: {{package}}. {{message}}',
38
+ [MESSAGE_ID_VERSION_MATCHES]:
39
+ 'There is a TODO match for package version: {{comparison}}. {{message}}',
40
+ [MESSAGE_ID_ENGINE_MATCHES]:
41
+ 'There is a TODO match for Node.js version: {{comparison}}. {{message}}',
42
+ [MESSAGE_ID_REMOVE_WHITESPACE]:
43
+ 'Avoid using whitespace on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
44
+ [MESSAGE_ID_MISSING_AT_SYMBOL]:
45
+ 'Missing \'@\' on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
46
+ ...baseRule.meta.messages,
47
+ [MESSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT]:
48
+ 'Unexpected \'{{matchedTerm}}\' comment without any conditions: \'{{comment}}\'.',
49
+ };
50
+
51
+ /** @param {string} dirname */
52
+ function getPackageHelpers(dirname) {
53
+ // We don't need to normalize the package.json data, because we are only using 2 properties and those 2 properties
54
+ // aren't validated by the normalization. But when this plugin is used in a monorepo, the name field in the
55
+ // package.json can be invalid and would make this plugin throw an error. See also #1871
56
+ /** @type {readPkgUp.ReadResult | undefined} */
57
+ let packageResult;
58
+ try {
59
+ packageResult = readPkgUp.sync({normalize: false, cwd: dirname});
60
+ } catch {
61
+ // This can happen if package.json files have comments in them etc.
62
+ packageResult = undefined;
63
+ }
64
+
65
+ const hasPackage = Boolean(packageResult);
66
+ const packageJson = packageResult ? packageResult.packageJson : {};
67
+
68
+ const packageDependencies = {
69
+ ...packageJson.dependencies,
70
+ ...packageJson.devDependencies,
71
+ };
72
+
73
+ function parseTodoWithArguments(string, {terms}) {
74
+ const lowerCaseString = string.toLowerCase();
75
+ const lowerCaseTerms = terms.map(term => term.toLowerCase());
76
+ const hasTerm = lowerCaseTerms.some(term => lowerCaseString.includes(term));
77
+
78
+ if (!hasTerm) {
79
+ return false;
80
+ }
81
+
82
+ const TODO_ARGUMENT_RE = /\[(?<rawArguments>[^}]+)]/i;
83
+ const result = TODO_ARGUMENT_RE.exec(string);
84
+
85
+ if (!result) {
86
+ return false;
87
+ }
88
+
89
+ const {rawArguments} = result.groups;
90
+
91
+ const parsedArguments = rawArguments
92
+ .split(',')
93
+ .map(argument => parseArgument(argument.trim()));
94
+
95
+ return createArgumentGroup(parsedArguments);
96
+ }
97
+
98
+ function parseArgument(argumentString, dirname) {
99
+ const {hasPackage} = getPackageHelpers(dirname);
100
+ if (ISO8601_DATE.test(argumentString)) {
101
+ return {
102
+ type: 'dates',
103
+ value: argumentString,
104
+ };
105
+ }
106
+
107
+ if (hasPackage && DEPENDENCY_INCLUSION_RE.test(argumentString)) {
108
+ const condition = argumentString[0] === '+' ? 'in' : 'out';
109
+ const name = argumentString.slice(1).trim();
110
+
111
+ return {
112
+ type: 'dependencies',
113
+ value: {
114
+ name,
115
+ condition,
116
+ },
117
+ };
118
+ }
119
+
120
+ if (hasPackage && VERSION_COMPARISON_RE.test(argumentString)) {
121
+ const {groups} = VERSION_COMPARISON_RE.exec(argumentString);
122
+ const name = groups.name.trim();
123
+ const condition = groups.condition.trim();
124
+ const version = groups.version.trim();
125
+
126
+ const hasEngineKeyword = name.indexOf('engine:') === 0;
127
+ const isNodeEngine = hasEngineKeyword && name === 'engine:node';
128
+
129
+ if (hasEngineKeyword && isNodeEngine) {
130
+ return {
131
+ type: 'engines',
132
+ value: {
133
+ condition,
134
+ version,
135
+ },
136
+ };
137
+ }
138
+
139
+ if (!hasEngineKeyword) {
140
+ return {
141
+ type: 'dependencies',
142
+ value: {
143
+ name,
144
+ condition,
145
+ version,
146
+ },
147
+ };
148
+ }
149
+ }
150
+
151
+ if (hasPackage && PKG_VERSION_RE.test(argumentString)) {
152
+ const result = PKG_VERSION_RE.exec(argumentString);
153
+ const {condition, version} = result.groups;
154
+
155
+ return {
156
+ type: 'packageVersions',
157
+ value: {
158
+ condition: condition.trim(),
159
+ version: version.trim(),
160
+ },
161
+ };
162
+ }
163
+
164
+ // Currently being ignored as integration tests pointed
165
+ // some TODO comments have `[random data like this]`
166
+ return {
167
+ type: 'unknowns',
168
+ value: argumentString,
169
+ };
170
+ }
171
+
172
+ function parseTodoMessage(todoString) {
173
+ // @example "TODO [...]: message here"
174
+ // @example "TODO [...] message here"
175
+ const argumentsEnd = todoString.indexOf(']');
176
+
177
+ const afterArguments = todoString.slice(argumentsEnd + 1).trim();
178
+
179
+ // Check if have to skip colon
180
+ // @example "TODO [...]: message here"
181
+ const dropColon = afterArguments[0] === ':';
182
+ if (dropColon) {
183
+ return afterArguments.slice(1).trim();
184
+ }
185
+
186
+ return afterArguments;
187
+ }
188
+
189
+ return {packageResult, hasPackage, packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments};
190
+ }
191
+
192
+ const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/;
193
+ const VERSION_COMPARISON_RE = /^(?<name>@?\S\/?\S+)@(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)/i;
194
+ const PKG_VERSION_RE = /^(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)\s*$/;
195
+ const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/;
196
+
197
+ function createArgumentGroup(arguments_) {
198
+ const groups = {};
199
+ for (const {value, type} of arguments_) {
200
+ groups[type] = groups[type] || [];
201
+ groups[type].push(value);
202
+ }
203
+
204
+ return groups;
205
+ }
206
+
207
+ function reachedDate(past, now) {
208
+ return Date.parse(past) < Date.parse(now);
209
+ }
210
+
211
+ function tryToCoerceVersion(rawVersion) {
212
+ // `version` in `package.json` and comment can't be empty
213
+ /* c8 ignore next 3 */
214
+ if (!rawVersion) {
215
+ return false;
216
+ }
217
+
218
+ let version = String(rawVersion);
219
+
220
+ // Remove leading things like `^1.0.0`, `>1.0.0`
221
+ const leadingNoises = [
222
+ '>=',
223
+ '<=',
224
+ '>',
225
+ '<',
226
+ '~',
227
+ '^',
228
+ ];
229
+ const foundTrailingNoise = leadingNoises.find(noise => version.startsWith(noise));
230
+ if (foundTrailingNoise) {
231
+ version = version.slice(foundTrailingNoise.length);
232
+ }
233
+
234
+ // Get only the first member for cases such as `1.0.0 - 2.9999.9999`
235
+ const parts = version.split(' ');
236
+ // We don't have this `package.json` to test
237
+ /* c8 ignore next 3 */
238
+ if (parts.length > 1) {
239
+ version = parts[0];
240
+ }
241
+
242
+ // We don't have this `package.json` to test
243
+ /* c8 ignore next 3 */
244
+ if (semver.valid(version)) {
245
+ return version;
246
+ }
247
+
248
+ try {
249
+ // Try to semver.parse a perfect match while semver.coerce tries to fix errors
250
+ // But coerce can't parse pre-releases.
251
+ return semver.parse(version) || semver.coerce(version);
252
+ } catch {
253
+ // We don't have this `package.json` to test
254
+ /* c8 ignore next 3 */
255
+ return false;
256
+ }
257
+ }
258
+
259
+ function semverComparisonForOperator(operator) {
260
+ return {
261
+ '>': semver.gt,
262
+ '>=': semver.gte,
263
+ }[operator];
264
+ }
265
+
266
+ /** @param {import('eslint').Rule.RuleContext} context */
267
+ const create = context => {
268
+ const options = {
269
+ terms: ['todo', 'fixme', 'xxx'],
270
+ ignore: [],
271
+ ignoreDatesOnPullRequests: true,
272
+ allowWarningComments: true,
273
+ date: new Date().toISOString().slice(0, 10),
274
+ ...context.options[0],
275
+ };
276
+
277
+ const ignoreRegexes = options.ignore.map(
278
+ pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
279
+ );
280
+
281
+ const dirname = path.dirname(context.filename);
282
+ const {packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments} = getPackageHelpers(dirname);
283
+
284
+ const {sourceCode} = context;
285
+ const comments = sourceCode.getAllComments();
286
+ const unusedComments = comments
287
+ .filter(token => token.type !== 'Shebang')
288
+ // Block comments come as one.
289
+ // Split for situations like this:
290
+ // /*
291
+ // * TODO [2999-01-01]: Validate this
292
+ // * TODO [2999-01-01]: And this
293
+ // * TODO [2999-01-01]: Also this
294
+ // */
295
+ .flatMap(comment =>
296
+ comment.value.split('\n').map(line => ({
297
+ ...comment,
298
+ value: line,
299
+ })),
300
+ ).filter(comment => processComment(comment));
301
+
302
+ // This is highly dependable on ESLint's `no-warning-comments` implementation.
303
+ // What we do is patch the parts we know the rule will use, `getAllComments`.
304
+ // Since we have priority, we leave only the comments that we didn't use.
305
+ const fakeContext = new Proxy(context, {
306
+ get(target, property, receiver) {
307
+ if (property === 'sourceCode') {
308
+ return {
309
+ ...sourceCode,
310
+ getAllComments: () => options.allowWarningComments ? [] : unusedComments,
311
+ };
312
+ }
313
+
314
+ return Reflect.get(target, property, receiver);
315
+ },
316
+ });
317
+ const rules = baseRule.create(fakeContext);
318
+
319
+ function processComment(comment) {
320
+ if (ignoreRegexes.some(ignore => ignore.test(comment.value))) {
321
+ return;
322
+ }
323
+
324
+ const parsed = parseTodoWithArguments(comment.value, options);
325
+
326
+ if (!parsed) {
327
+ return true;
328
+ }
329
+
330
+ // Count if there are valid properties.
331
+ // Otherwise, it's a useless TODO and falls back to `no-warning-comments`.
332
+ let uses = 0;
333
+
334
+ const {
335
+ packageVersions = [],
336
+ dates = [],
337
+ dependencies = [],
338
+ engines = [],
339
+ unknowns = [],
340
+ } = parsed;
341
+
342
+ if (dates.length > 1) {
343
+ uses++;
344
+ context.report({
345
+ loc: comment.loc,
346
+ messageId: MESSAGE_ID_AVOID_MULTIPLE_DATES,
347
+ data: {
348
+ expirationDates: dates.join(', '),
349
+ message: parseTodoMessage(comment.value),
350
+ },
351
+ });
352
+ } else if (dates.length === 1) {
353
+ uses++;
354
+ const [expirationDate] = dates;
355
+
356
+ const shouldIgnore = options.ignoreDatesOnPullRequests && ci.isPR;
357
+ if (!shouldIgnore && reachedDate(expirationDate, options.date)) {
358
+ context.report({
359
+ loc: comment.loc,
360
+ messageId: MESSAGE_ID_EXPIRED_TODO,
361
+ data: {
362
+ expirationDate,
363
+ message: parseTodoMessage(comment.value),
364
+ },
365
+ });
366
+ }
367
+ }
368
+
369
+ if (packageVersions.length > 1) {
370
+ uses++;
371
+ context.report({
372
+ loc: comment.loc,
373
+ messageId: MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS,
374
+ data: {
375
+ versions: packageVersions
376
+ .map(({condition, version}) => `${condition}${version}`)
377
+ .join(', '),
378
+ message: parseTodoMessage(comment.value),
379
+ },
380
+ });
381
+ } else if (packageVersions.length === 1) {
382
+ uses++;
383
+ const [{condition, version}] = packageVersions;
384
+
385
+ const packageVersion = tryToCoerceVersion(packageJson.version);
386
+ const decidedPackageVersion = tryToCoerceVersion(version);
387
+
388
+ const compare = semverComparisonForOperator(condition);
389
+ if (packageVersion && compare(packageVersion, decidedPackageVersion)) {
390
+ context.report({
391
+ loc: comment.loc,
392
+ messageId: MESSAGE_ID_REACHED_PACKAGE_VERSION,
393
+ data: {
394
+ comparison: `${condition}${version}`,
395
+ message: parseTodoMessage(comment.value),
396
+ },
397
+ });
398
+ }
399
+ }
400
+
401
+ // Inclusion: 'in', 'out'
402
+ // Comparison: '>', '>='
403
+ for (const dependency of dependencies) {
404
+ uses++;
405
+ const targetPackageRawVersion = packageDependencies[dependency.name];
406
+ const hasTargetPackage = Boolean(targetPackageRawVersion);
407
+
408
+ const isInclusion = ['in', 'out'].includes(dependency.condition);
409
+ if (isInclusion) {
410
+ const [trigger, messageId]
411
+ = dependency.condition === 'in'
412
+ ? [hasTargetPackage, MESSAGE_ID_HAVE_PACKAGE]
413
+ : [!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];
414
+
415
+ if (trigger) {
416
+ context.report({
417
+ loc: comment.loc,
418
+ messageId,
419
+ data: {
420
+ package: dependency.name,
421
+ message: parseTodoMessage(comment.value),
422
+ },
423
+ });
424
+ }
425
+
426
+ continue;
427
+ }
428
+
429
+ const todoVersion = tryToCoerceVersion(dependency.version);
430
+ const targetPackageVersion = tryToCoerceVersion(targetPackageRawVersion);
431
+
432
+ /* c8 ignore start */
433
+ if (!hasTargetPackage || !targetPackageVersion) {
434
+ // Can't compare `¯\_(ツ)_/¯`
435
+ continue;
436
+ }
437
+ /* c8 ignore end */
438
+
439
+ const compare = semverComparisonForOperator(dependency.condition);
440
+
441
+ if (compare(targetPackageVersion, todoVersion)) {
442
+ context.report({
443
+ loc: comment.loc,
444
+ messageId: MESSAGE_ID_VERSION_MATCHES,
445
+ data: {
446
+ comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`,
447
+ message: parseTodoMessage(comment.value),
448
+ },
449
+ });
450
+ }
451
+ }
452
+
453
+ const packageEngines = packageJson.engines || {};
454
+
455
+ for (const engine of engines) {
456
+ uses++;
457
+
458
+ const targetPackageRawEngineVersion = packageEngines.node;
459
+ const hasTargetEngine = Boolean(targetPackageRawEngineVersion);
460
+
461
+ /* c8 ignore next 3 */
462
+ if (!hasTargetEngine) {
463
+ continue;
464
+ }
465
+
466
+ const todoEngine = tryToCoerceVersion(engine.version);
467
+ const targetPackageEngineVersion = tryToCoerceVersion(
468
+ targetPackageRawEngineVersion,
469
+ );
470
+
471
+ const compare = semverComparisonForOperator(engine.condition);
472
+
473
+ if (compare(targetPackageEngineVersion, todoEngine)) {
474
+ context.report({
475
+ loc: comment.loc,
476
+ messageId: MESSAGE_ID_ENGINE_MATCHES,
477
+ data: {
478
+ comparison: `node${engine.condition}${engine.version}`,
479
+ message: parseTodoMessage(comment.value),
480
+ },
481
+ });
482
+ }
483
+ }
484
+
485
+ for (const unknown of unknowns) {
486
+ // In this case, check if there's just an '@' missing before a '>' or '>='.
487
+ const hasAt = unknown.includes('@');
488
+ const comparisonIndex = unknown.indexOf('>');
489
+
490
+ if (!hasAt && comparisonIndex !== -1) {
491
+ const testString = `${unknown.slice(
492
+ 0,
493
+ comparisonIndex,
494
+ )}@${unknown.slice(comparisonIndex)}`;
495
+
496
+ if (parseArgument(testString).type !== 'unknowns') {
497
+ uses++;
498
+ context.report({
499
+ loc: comment.loc,
500
+ messageId: MESSAGE_ID_MISSING_AT_SYMBOL,
501
+ data: {
502
+ original: unknown,
503
+ fix: testString,
504
+ message: parseTodoMessage(comment.value),
505
+ },
506
+ });
507
+ continue;
508
+ }
509
+ }
510
+
511
+ const withoutWhitespace = unknown.replaceAll(' ', '');
512
+
513
+ if (parseArgument(withoutWhitespace).type !== 'unknowns') {
514
+ uses++;
515
+ context.report({
516
+ loc: comment.loc,
517
+ messageId: MESSAGE_ID_REMOVE_WHITESPACE,
518
+ data: {
519
+ original: unknown,
520
+ fix: withoutWhitespace,
521
+ message: parseTodoMessage(comment.value),
522
+ },
523
+ });
524
+ continue;
525
+ }
526
+ }
527
+
528
+ return uses === 0;
529
+ }
530
+
531
+ return {
532
+ Program() {
533
+ rules.Program(); // eslint-disable-line new-cap
534
+ },
535
+ };
536
+ };
537
+
538
+ const schema = [
539
+ {
540
+ type: 'object',
541
+ additionalProperties: false,
542
+ properties: {
543
+ terms: {
544
+ type: 'array',
545
+ items: {
546
+ type: 'string',
547
+ },
548
+ },
549
+ ignore: {
550
+ type: 'array',
551
+ uniqueItems: true,
552
+ },
553
+ ignoreDatesOnPullRequests: {
554
+ type: 'boolean',
555
+ default: true,
556
+ },
557
+ allowWarningComments: {
558
+ type: 'boolean',
559
+ default: false,
560
+ },
561
+ date: {
562
+ type: 'string',
563
+ format: 'date',
564
+ },
565
+ },
566
+ },
567
+ ];
568
+
569
+ /** @type {import('eslint').Rule.RuleModule} */
570
+ module.exports = {
571
+ create,
572
+ meta: {
573
+ type: 'suggestion',
574
+ docs: {
575
+ description: 'Add expiration conditions to TODO comments.',
576
+ },
577
+ schema,
578
+ messages,
579
+ },
580
+ };