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.
- package/configs/all.js +6 -0
- package/configs/flat-config-base.js +10 -0
- package/configs/legacy-config-base.js +10 -0
- package/configs/recommended.js +117 -0
- package/index.js +91 -0
- package/license +9 -0
- package/package.json +186 -4
- package/readme.md +356 -0
- package/rules/ast/call-or-new-expression.js +127 -0
- package/rules/ast/function-types.js +5 -0
- package/rules/ast/index.js +39 -0
- package/rules/ast/is-arrow-function-body.js +7 -0
- package/rules/ast/is-empty-node.js +20 -0
- package/rules/ast/is-expression-statement.js +11 -0
- package/rules/ast/is-function.js +8 -0
- package/rules/ast/is-member-expression.js +101 -0
- package/rules/ast/is-method-call.js +65 -0
- package/rules/ast/is-reference-identifier.js +156 -0
- package/rules/ast/is-static-require.js +14 -0
- package/rules/ast/is-undefined.js +7 -0
- package/rules/ast/literal.js +29 -0
- package/rules/better-regex.js +144 -0
- package/rules/catch-error-name.js +136 -0
- package/rules/consistent-destructuring.js +168 -0
- package/rules/consistent-function-scoping.js +223 -0
- package/rules/custom-error-definition.js +215 -0
- package/rules/empty-brace-spaces.js +72 -0
- package/rules/error-message.js +104 -0
- package/rules/escape-case.js +63 -0
- package/rules/expiring-todo-comments.js +580 -0
- package/rules/explicit-length-check.js +229 -0
- package/rules/filename-case.js +258 -0
- package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
- package/rules/fix/append-argument.js +20 -0
- package/rules/fix/extend-fix-range.js +15 -0
- package/rules/fix/fix-space-around-keywords.js +35 -0
- package/rules/fix/index.js +23 -0
- package/rules/fix/remove-argument.js +32 -0
- package/rules/fix/remove-member-expression-property.js +11 -0
- package/rules/fix/remove-method-call.js +20 -0
- package/rules/fix/remove-parentheses.js +11 -0
- package/rules/fix/remove-spaces-after.js +14 -0
- package/rules/fix/rename-variable.js +9 -0
- package/rules/fix/replace-argument.js +8 -0
- package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
- package/rules/fix/replace-reference-identifier.js +35 -0
- package/rules/fix/replace-string-literal.js +11 -0
- package/rules/fix/replace-string-raw.js +14 -0
- package/rules/fix/replace-template-element.js +11 -0
- package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
- package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
- package/rules/import-style.js +364 -0
- package/rules/new-for-builtins.js +85 -0
- package/rules/no-abusive-eslint-disable.js +48 -0
- package/rules/no-array-callback-reference.js +256 -0
- package/rules/no-array-for-each.js +473 -0
- package/rules/no-array-method-this-argument.js +188 -0
- package/rules/no-array-push-push.js +144 -0
- package/rules/no-array-reduce.js +126 -0
- package/rules/no-await-expression-member.js +90 -0
- package/rules/no-console-spaces.js +86 -0
- package/rules/no-document-cookie.js +25 -0
- package/rules/no-empty-file.js +57 -0
- package/rules/no-for-loop.js +427 -0
- package/rules/no-hex-escape.js +46 -0
- package/rules/no-instanceof-array.js +65 -0
- package/rules/no-invalid-remove-event-listener.js +60 -0
- package/rules/no-keyword-prefix.js +199 -0
- package/rules/no-lonely-if.js +151 -0
- package/rules/no-negated-condition.js +144 -0
- package/rules/no-nested-ternary.js +58 -0
- package/rules/no-new-array.js +104 -0
- package/rules/no-new-buffer.js +98 -0
- package/rules/no-null.js +153 -0
- package/rules/no-object-as-default-parameter.js +50 -0
- package/rules/no-process-exit.js +104 -0
- package/rules/no-static-only-class.js +224 -0
- package/rules/no-thenable.js +198 -0
- package/rules/no-this-assignment.js +38 -0
- package/rules/no-typeof-undefined.js +143 -0
- package/rules/no-unnecessary-await.js +107 -0
- package/rules/no-unnecessary-polyfills.js +176 -0
- package/rules/no-unreadable-array-destructuring.js +83 -0
- package/rules/no-unreadable-iife.js +45 -0
- package/rules/no-unused-properties.js +238 -0
- package/rules/no-useless-fallback-in-spread.js +68 -0
- package/rules/no-useless-length-check.js +152 -0
- package/rules/no-useless-promise-resolve-reject.js +212 -0
- package/rules/no-useless-spread.js +381 -0
- package/rules/no-useless-switch-case.js +71 -0
- package/rules/no-useless-undefined.js +301 -0
- package/rules/no-zero-fractions.js +79 -0
- package/rules/number-literal-case.js +52 -0
- package/rules/numeric-separators-style.js +181 -0
- package/rules/prefer-add-event-listener.js +188 -0
- package/rules/prefer-array-find.js +423 -0
- package/rules/prefer-array-flat-map.js +82 -0
- package/rules/prefer-array-flat.js +279 -0
- package/rules/prefer-array-index-of.js +32 -0
- package/rules/prefer-array-some.js +157 -0
- package/rules/prefer-at.js +374 -0
- package/rules/prefer-blob-reading-methods.js +45 -0
- package/rules/prefer-code-point.js +67 -0
- package/rules/prefer-date-now.js +135 -0
- package/rules/prefer-default-parameters.js +219 -0
- package/rules/prefer-dom-node-append.js +48 -0
- package/rules/prefer-dom-node-dataset.js +120 -0
- package/rules/prefer-dom-node-remove.js +122 -0
- package/rules/prefer-dom-node-text-content.js +75 -0
- package/rules/prefer-event-target.js +117 -0
- package/rules/prefer-export-from.js +413 -0
- package/rules/prefer-includes.js +98 -0
- package/rules/prefer-json-parse-buffer.js +159 -0
- package/rules/prefer-keyboard-event-key.js +186 -0
- package/rules/prefer-logical-operator-over-ternary.js +159 -0
- package/rules/prefer-math-trunc.js +109 -0
- package/rules/prefer-modern-dom-apis.js +141 -0
- package/rules/prefer-modern-math-apis.js +212 -0
- package/rules/prefer-module.js +349 -0
- package/rules/prefer-native-coercion-functions.js +185 -0
- package/rules/prefer-negative-index.js +213 -0
- package/rules/prefer-node-protocol.js +61 -0
- package/rules/prefer-number-properties.js +126 -0
- package/rules/prefer-object-from-entries.js +252 -0
- package/rules/prefer-optional-catch-binding.js +75 -0
- package/rules/prefer-prototype-methods.js +88 -0
- package/rules/prefer-query-selector.js +135 -0
- package/rules/prefer-reflect-apply.js +97 -0
- package/rules/prefer-regexp-test.js +156 -0
- package/rules/prefer-set-has.js +186 -0
- package/rules/prefer-set-size.js +103 -0
- package/rules/prefer-spread.js +529 -0
- package/rules/prefer-string-replace-all.js +145 -0
- package/rules/prefer-string-slice.js +182 -0
- package/rules/prefer-string-starts-ends-with.js +199 -0
- package/rules/prefer-string-trim-start-end.js +44 -0
- package/rules/prefer-switch.js +344 -0
- package/rules/prefer-ternary.js +282 -0
- package/rules/prefer-top-level-await.js +152 -0
- package/rules/prefer-type-error.js +151 -0
- package/rules/prevent-abbreviations.js +645 -0
- package/rules/relative-url-style.js +168 -0
- package/rules/require-array-join-separator.js +63 -0
- package/rules/require-number-to-fixed-digits-argument.js +54 -0
- package/rules/require-post-message-target-origin.js +71 -0
- package/rules/shared/abbreviations.js +262 -0
- package/rules/shared/dom-events.js +275 -0
- package/rules/shared/event-keys.js +52 -0
- package/rules/shared/negative-index.js +46 -0
- package/rules/shared/simple-array-search-rule.js +128 -0
- package/rules/shared/typed-array.js +16 -0
- package/rules/string-content.js +187 -0
- package/rules/switch-case-braces.js +109 -0
- package/rules/template-indent.js +219 -0
- package/rules/text-encoding-identifier-case.js +108 -0
- package/rules/throw-new-error.js +53 -0
- package/rules/utils/array-or-object-prototype-property.js +63 -0
- package/rules/utils/assert-token.js +32 -0
- package/rules/utils/avoid-capture.js +146 -0
- package/rules/utils/boolean.js +92 -0
- package/rules/utils/builtins.js +36 -0
- package/rules/utils/cartesian-product-samples.js +24 -0
- package/rules/utils/create-deprecated-rules.js +25 -0
- package/rules/utils/escape-string.js +26 -0
- package/rules/utils/escape-template-element-raw.js +6 -0
- package/rules/utils/get-ancestor.js +20 -0
- package/rules/utils/get-builtin-rule.js +7 -0
- package/rules/utils/get-call-expression-arguments-text.js +21 -0
- package/rules/utils/get-class-head-location.js +22 -0
- package/rules/utils/get-documentation-url.js +10 -0
- package/rules/utils/get-indent-string.js +11 -0
- package/rules/utils/get-previous-node.js +24 -0
- package/rules/utils/get-references.js +9 -0
- package/rules/utils/get-scopes.js +14 -0
- package/rules/utils/get-switch-case-head-location.js +21 -0
- package/rules/utils/get-variable-identifiers.js +7 -0
- package/rules/utils/global-reference-tracker.js +72 -0
- package/rules/utils/has-optional-chain-element.js +21 -0
- package/rules/utils/has-same-range.js +7 -0
- package/rules/utils/index.js +53 -0
- package/rules/utils/is-function-self-used-inside.js +43 -0
- package/rules/utils/is-left-hand-side.js +22 -0
- package/rules/utils/is-logical-expression.js +16 -0
- package/rules/utils/is-method-named.js +9 -0
- package/rules/utils/is-new-expression-with-parentheses.js +26 -0
- package/rules/utils/is-node-matches.js +53 -0
- package/rules/utils/is-node-value-not-dom-node.js +21 -0
- package/rules/utils/is-node-value-not-function.js +42 -0
- package/rules/utils/is-number.js +224 -0
- package/rules/utils/is-object-method.js +11 -0
- package/rules/utils/is-on-same-line.js +7 -0
- package/rules/utils/is-same-identifier.js +8 -0
- package/rules/utils/is-same-reference.js +173 -0
- package/rules/utils/is-shadowed.js +33 -0
- package/rules/utils/is-shorthand-export-local.js +9 -0
- package/rules/utils/is-shorthand-import-local.js +9 -0
- package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
- package/rules/utils/is-shorthand-property-value.js +8 -0
- package/rules/utils/is-value-not-usable.js +5 -0
- package/rules/utils/lodash.js +1589 -0
- package/rules/utils/needs-semicolon.js +114 -0
- package/rules/utils/numeric.js +53 -0
- package/rules/utils/parentheses.js +73 -0
- package/rules/utils/resolve-variable-name.js +20 -0
- package/rules/utils/rule.js +190 -0
- package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
- package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
- package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
- package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
- package/rules/utils/singular.js +18 -0
- package/rules/utils/to-location.js +21 -0
- 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
|
+
};
|