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,645 @@
|
|
1
|
+
'use strict';
|
2
|
+
const path = require('node:path');
|
3
|
+
const {defaultsDeep, upperFirst, lowerFirst} = require('./utils/lodash.js');
|
4
|
+
const avoidCapture = require('./utils/avoid-capture.js');
|
5
|
+
const cartesianProductSamples = require('./utils/cartesian-product-samples.js');
|
6
|
+
const isShorthandPropertyValue = require('./utils/is-shorthand-property-value.js');
|
7
|
+
const isShorthandImportLocal = require('./utils/is-shorthand-import-local.js');
|
8
|
+
const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
|
9
|
+
const {defaultReplacements, defaultAllowList, defaultIgnore} = require('./shared/abbreviations.js');
|
10
|
+
const {renameVariable} = require('./fix/index.js');
|
11
|
+
const getScopes = require('./utils/get-scopes.js');
|
12
|
+
const {isStaticRequire} = require('./ast/index.js');
|
13
|
+
|
14
|
+
const MESSAGE_ID_REPLACE = 'replace';
|
15
|
+
const MESSAGE_ID_SUGGESTION = 'suggestion';
|
16
|
+
const anotherNameMessage = 'A more descriptive name will do too.';
|
17
|
+
const messages = {
|
18
|
+
[MESSAGE_ID_REPLACE]: `The {{nameTypeText}} \`{{discouragedName}}\` should be named \`{{replacement}}\`. ${anotherNameMessage}`,
|
19
|
+
[MESSAGE_ID_SUGGESTION]: `Please rename the {{nameTypeText}} \`{{discouragedName}}\`. Suggested names are: {{replacementsText}}. ${anotherNameMessage}`,
|
20
|
+
};
|
21
|
+
|
22
|
+
const isUpperCase = string => string === string.toUpperCase();
|
23
|
+
const isUpperFirst = string => isUpperCase(string[0]);
|
24
|
+
|
25
|
+
const prepareOptions = ({
|
26
|
+
checkProperties = false,
|
27
|
+
checkVariables = true,
|
28
|
+
|
29
|
+
checkDefaultAndNamespaceImports = 'internal',
|
30
|
+
checkShorthandImports = 'internal',
|
31
|
+
checkShorthandProperties = false,
|
32
|
+
|
33
|
+
checkFilenames = true,
|
34
|
+
|
35
|
+
extendDefaultReplacements = true,
|
36
|
+
replacements = {},
|
37
|
+
|
38
|
+
extendDefaultAllowList = true,
|
39
|
+
allowList = {},
|
40
|
+
|
41
|
+
ignore = [],
|
42
|
+
} = {}) => {
|
43
|
+
const mergedReplacements = extendDefaultReplacements
|
44
|
+
? defaultsDeep({}, replacements, defaultReplacements)
|
45
|
+
: replacements;
|
46
|
+
|
47
|
+
const mergedAllowList = extendDefaultAllowList
|
48
|
+
? defaultsDeep({}, allowList, defaultAllowList)
|
49
|
+
: allowList;
|
50
|
+
|
51
|
+
ignore = [...defaultIgnore, ...ignore];
|
52
|
+
|
53
|
+
ignore = ignore.map(
|
54
|
+
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
|
55
|
+
);
|
56
|
+
|
57
|
+
return {
|
58
|
+
checkProperties,
|
59
|
+
checkVariables,
|
60
|
+
|
61
|
+
checkDefaultAndNamespaceImports,
|
62
|
+
checkShorthandImports,
|
63
|
+
checkShorthandProperties,
|
64
|
+
|
65
|
+
checkFilenames,
|
66
|
+
|
67
|
+
replacements: new Map(
|
68
|
+
Object.entries(mergedReplacements).map(
|
69
|
+
([discouragedName, replacements]) =>
|
70
|
+
[discouragedName, new Map(Object.entries(replacements))],
|
71
|
+
),
|
72
|
+
),
|
73
|
+
allowList: new Map(Object.entries(mergedAllowList)),
|
74
|
+
|
75
|
+
ignore,
|
76
|
+
};
|
77
|
+
};
|
78
|
+
|
79
|
+
const getWordReplacements = (word, {replacements, allowList}) => {
|
80
|
+
// Skip constants and allowList
|
81
|
+
if (isUpperCase(word) || allowList.get(word)) {
|
82
|
+
return [];
|
83
|
+
}
|
84
|
+
|
85
|
+
const replacement = replacements.get(lowerFirst(word))
|
86
|
+
|| replacements.get(word)
|
87
|
+
|| replacements.get(upperFirst(word));
|
88
|
+
|
89
|
+
let wordReplacement = [];
|
90
|
+
if (replacement) {
|
91
|
+
const transform = isUpperFirst(word) ? upperFirst : lowerFirst;
|
92
|
+
wordReplacement = [...replacement.keys()]
|
93
|
+
.filter(name => replacement.get(name))
|
94
|
+
.map(name => transform(name));
|
95
|
+
}
|
96
|
+
|
97
|
+
return wordReplacement.length > 0 ? wordReplacement.sort() : [];
|
98
|
+
};
|
99
|
+
|
100
|
+
const getNameReplacements = (name, options, limit = 3) => {
|
101
|
+
const {allowList, ignore} = options;
|
102
|
+
|
103
|
+
// Skip constants and allowList
|
104
|
+
if (isUpperCase(name) || allowList.get(name) || ignore.some(regexp => regexp.test(name))) {
|
105
|
+
return {total: 0};
|
106
|
+
}
|
107
|
+
|
108
|
+
// Find exact replacements
|
109
|
+
const exactReplacements = getWordReplacements(name, options);
|
110
|
+
|
111
|
+
if (exactReplacements.length > 0) {
|
112
|
+
return {
|
113
|
+
total: exactReplacements.length,
|
114
|
+
samples: exactReplacements.slice(0, limit),
|
115
|
+
};
|
116
|
+
}
|
117
|
+
|
118
|
+
// Split words
|
119
|
+
const words = name.split(/(?=[^a-z])|(?<=[^A-Za-z])/).filter(Boolean);
|
120
|
+
|
121
|
+
let hasReplacements = false;
|
122
|
+
const combinations = words.map(word => {
|
123
|
+
const wordReplacements = getWordReplacements(word, options);
|
124
|
+
|
125
|
+
if (wordReplacements.length > 0) {
|
126
|
+
hasReplacements = true;
|
127
|
+
return wordReplacements;
|
128
|
+
}
|
129
|
+
|
130
|
+
return [word];
|
131
|
+
});
|
132
|
+
|
133
|
+
// No replacements for any word
|
134
|
+
if (!hasReplacements) {
|
135
|
+
return {total: 0};
|
136
|
+
}
|
137
|
+
|
138
|
+
const {
|
139
|
+
total,
|
140
|
+
samples,
|
141
|
+
} = cartesianProductSamples(combinations, limit);
|
142
|
+
|
143
|
+
// `retVal` -> `['returnValue', 'Value']` -> `['returnValue']`
|
144
|
+
for (const parts of samples) {
|
145
|
+
for (let index = parts.length - 1; index > 0; index--) {
|
146
|
+
const word = parts[index];
|
147
|
+
if (/^[A-Za-z]+$/.test(word) && parts[index - 1].endsWith(parts[index])) {
|
148
|
+
parts.splice(index, 1);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
return {
|
154
|
+
total,
|
155
|
+
samples: samples.map(words => words.join('')),
|
156
|
+
};
|
157
|
+
};
|
158
|
+
|
159
|
+
const getMessage = (discouragedName, replacements, nameTypeText) => {
|
160
|
+
const {total, samples = []} = replacements;
|
161
|
+
|
162
|
+
if (total === 1) {
|
163
|
+
return {
|
164
|
+
messageId: MESSAGE_ID_REPLACE,
|
165
|
+
data: {
|
166
|
+
nameTypeText,
|
167
|
+
discouragedName,
|
168
|
+
replacement: samples[0],
|
169
|
+
},
|
170
|
+
};
|
171
|
+
}
|
172
|
+
|
173
|
+
let replacementsText = samples
|
174
|
+
.map(replacement => `\`${replacement}\``)
|
175
|
+
.join(', ');
|
176
|
+
|
177
|
+
const omittedReplacementsCount = total - samples.length;
|
178
|
+
if (omittedReplacementsCount > 0) {
|
179
|
+
replacementsText += `, ... (${omittedReplacementsCount > 99 ? '99+' : omittedReplacementsCount} more omitted)`;
|
180
|
+
}
|
181
|
+
|
182
|
+
return {
|
183
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
184
|
+
data: {
|
185
|
+
nameTypeText,
|
186
|
+
discouragedName,
|
187
|
+
replacementsText,
|
188
|
+
},
|
189
|
+
};
|
190
|
+
};
|
191
|
+
|
192
|
+
const isExportedIdentifier = identifier => {
|
193
|
+
if (
|
194
|
+
identifier.parent.type === 'VariableDeclarator'
|
195
|
+
&& identifier.parent.id === identifier
|
196
|
+
) {
|
197
|
+
return (
|
198
|
+
identifier.parent.parent.type === 'VariableDeclaration'
|
199
|
+
&& identifier.parent.parent.parent.type === 'ExportNamedDeclaration'
|
200
|
+
);
|
201
|
+
}
|
202
|
+
|
203
|
+
if (
|
204
|
+
identifier.parent.type === 'FunctionDeclaration'
|
205
|
+
&& identifier.parent.id === identifier
|
206
|
+
) {
|
207
|
+
return identifier.parent.parent.type === 'ExportNamedDeclaration';
|
208
|
+
}
|
209
|
+
|
210
|
+
if (
|
211
|
+
identifier.parent.type === 'ClassDeclaration'
|
212
|
+
&& identifier.parent.id === identifier
|
213
|
+
) {
|
214
|
+
return identifier.parent.parent.type === 'ExportNamedDeclaration';
|
215
|
+
}
|
216
|
+
|
217
|
+
if (
|
218
|
+
identifier.parent.type === 'TSTypeAliasDeclaration'
|
219
|
+
&& identifier.parent.id === identifier
|
220
|
+
) {
|
221
|
+
return identifier.parent.parent.type === 'ExportNamedDeclaration';
|
222
|
+
}
|
223
|
+
|
224
|
+
return false;
|
225
|
+
};
|
226
|
+
|
227
|
+
const shouldFix = variable => getVariableIdentifiers(variable)
|
228
|
+
.every(identifier =>
|
229
|
+
!isExportedIdentifier(identifier)
|
230
|
+
// In typescript parser, only `JSXOpeningElement` is added to variable
|
231
|
+
// `<foo></foo>` -> `<bar></foo>` will cause parse error
|
232
|
+
&& identifier.type !== 'JSXIdentifier',
|
233
|
+
);
|
234
|
+
|
235
|
+
const isDefaultOrNamespaceImportName = identifier => {
|
236
|
+
if (
|
237
|
+
identifier.parent.type === 'ImportDefaultSpecifier'
|
238
|
+
&& identifier.parent.local === identifier
|
239
|
+
) {
|
240
|
+
return true;
|
241
|
+
}
|
242
|
+
|
243
|
+
if (
|
244
|
+
identifier.parent.type === 'ImportNamespaceSpecifier'
|
245
|
+
&& identifier.parent.local === identifier
|
246
|
+
) {
|
247
|
+
return true;
|
248
|
+
}
|
249
|
+
|
250
|
+
if (
|
251
|
+
identifier.parent.type === 'ImportSpecifier'
|
252
|
+
&& identifier.parent.local === identifier
|
253
|
+
&& identifier.parent.imported.type === 'Identifier'
|
254
|
+
&& identifier.parent.imported.name === 'default'
|
255
|
+
) {
|
256
|
+
return true;
|
257
|
+
}
|
258
|
+
|
259
|
+
if (
|
260
|
+
identifier.parent.type === 'VariableDeclarator'
|
261
|
+
&& identifier.parent.id === identifier
|
262
|
+
&& isStaticRequire(identifier.parent.init)
|
263
|
+
) {
|
264
|
+
return true;
|
265
|
+
}
|
266
|
+
|
267
|
+
return false;
|
268
|
+
};
|
269
|
+
|
270
|
+
const isClassVariable = variable => {
|
271
|
+
if (variable.defs.length !== 1) {
|
272
|
+
return false;
|
273
|
+
}
|
274
|
+
|
275
|
+
const [definition] = variable.defs;
|
276
|
+
|
277
|
+
return definition.type === 'ClassName';
|
278
|
+
};
|
279
|
+
|
280
|
+
const shouldReportIdentifierAsProperty = identifier => {
|
281
|
+
if (
|
282
|
+
identifier.parent.type === 'MemberExpression'
|
283
|
+
&& identifier.parent.property === identifier
|
284
|
+
&& !identifier.parent.computed
|
285
|
+
&& identifier.parent.parent.type === 'AssignmentExpression'
|
286
|
+
&& identifier.parent.parent.left === identifier.parent
|
287
|
+
) {
|
288
|
+
return true;
|
289
|
+
}
|
290
|
+
|
291
|
+
if (
|
292
|
+
identifier.parent.type === 'Property'
|
293
|
+
&& identifier.parent.key === identifier
|
294
|
+
&& !identifier.parent.computed
|
295
|
+
&& !identifier.parent.shorthand // Shorthand properties are reported and fixed as variables
|
296
|
+
&& identifier.parent.parent.type === 'ObjectExpression'
|
297
|
+
) {
|
298
|
+
return true;
|
299
|
+
}
|
300
|
+
|
301
|
+
if (
|
302
|
+
identifier.parent.type === 'ExportSpecifier'
|
303
|
+
&& identifier.parent.exported === identifier
|
304
|
+
&& identifier.parent.local !== identifier // Same as shorthand properties above
|
305
|
+
) {
|
306
|
+
return true;
|
307
|
+
}
|
308
|
+
|
309
|
+
if (
|
310
|
+
(
|
311
|
+
identifier.parent.type === 'MethodDefinition'
|
312
|
+
|| identifier.parent.type === 'PropertyDefinition'
|
313
|
+
)
|
314
|
+
&& identifier.parent.key === identifier
|
315
|
+
&& !identifier.parent.computed
|
316
|
+
) {
|
317
|
+
return true;
|
318
|
+
}
|
319
|
+
|
320
|
+
return false;
|
321
|
+
};
|
322
|
+
|
323
|
+
const isInternalImport = node => {
|
324
|
+
let source = '';
|
325
|
+
|
326
|
+
if (node.type === 'Variable') {
|
327
|
+
source = node.node.init.arguments[0].value;
|
328
|
+
} else if (node.type === 'ImportBinding') {
|
329
|
+
source = node.parent.source.value;
|
330
|
+
}
|
331
|
+
|
332
|
+
return (
|
333
|
+
!source.includes('node_modules')
|
334
|
+
&& (source.startsWith('.') || source.startsWith('/'))
|
335
|
+
);
|
336
|
+
};
|
337
|
+
|
338
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
339
|
+
const create = context => {
|
340
|
+
const options = prepareOptions(context.options[0]);
|
341
|
+
const filenameWithExtension = context.physicalFilename;
|
342
|
+
|
343
|
+
// A `class` declaration produces two variables in two scopes:
|
344
|
+
// the inner class scope, and the outer one (wherever the class is declared).
|
345
|
+
// This map holds the outer ones to be later processed when the inner one is encountered.
|
346
|
+
// For why this is not a eslint issue see https://github.com/eslint/eslint-scope/issues/48#issuecomment-464358754
|
347
|
+
const identifierToOuterClassVariable = new WeakMap();
|
348
|
+
|
349
|
+
const checkPossiblyWeirdClassVariable = variable => {
|
350
|
+
if (isClassVariable(variable)) {
|
351
|
+
if (variable.scope.type === 'class') { // The inner class variable
|
352
|
+
const [definition] = variable.defs;
|
353
|
+
const outerClassVariable = identifierToOuterClassVariable.get(definition.name);
|
354
|
+
|
355
|
+
if (!outerClassVariable) {
|
356
|
+
return checkVariable(variable);
|
357
|
+
}
|
358
|
+
|
359
|
+
// Create a normal-looking variable (like a `var` or a `function`)
|
360
|
+
// For which a single `variable` holds all references, unlike with a `class`
|
361
|
+
const combinedReferencesVariable = {
|
362
|
+
name: variable.name,
|
363
|
+
scope: variable.scope,
|
364
|
+
defs: variable.defs,
|
365
|
+
identifiers: variable.identifiers,
|
366
|
+
references: [...variable.references, ...outerClassVariable.references],
|
367
|
+
};
|
368
|
+
|
369
|
+
// Call the common checker with the newly forged normalized class variable
|
370
|
+
return checkVariable(combinedReferencesVariable);
|
371
|
+
}
|
372
|
+
|
373
|
+
// The outer class variable, we save it for later, when it's inner counterpart is encountered
|
374
|
+
const [definition] = variable.defs;
|
375
|
+
identifierToOuterClassVariable.set(definition.name, variable);
|
376
|
+
|
377
|
+
return;
|
378
|
+
}
|
379
|
+
|
380
|
+
return checkVariable(variable);
|
381
|
+
};
|
382
|
+
|
383
|
+
// Holds a map from a `Scope` to a `Set` of new variable names generated by our fixer.
|
384
|
+
// Used to avoid generating duplicate names, see for instance `let errCb, errorCb` test.
|
385
|
+
const scopeToNamesGeneratedByFixer = new WeakMap();
|
386
|
+
const isSafeName = (name, scopes) => scopes.every(scope => {
|
387
|
+
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
388
|
+
return !generatedNames || !generatedNames.has(name);
|
389
|
+
});
|
390
|
+
|
391
|
+
const checkVariable = variable => {
|
392
|
+
if (variable.defs.length === 0) {
|
393
|
+
return;
|
394
|
+
}
|
395
|
+
|
396
|
+
const [definition] = variable.defs;
|
397
|
+
|
398
|
+
if (isDefaultOrNamespaceImportName(definition.name)) {
|
399
|
+
if (!options.checkDefaultAndNamespaceImports) {
|
400
|
+
return;
|
401
|
+
}
|
402
|
+
|
403
|
+
if (
|
404
|
+
options.checkDefaultAndNamespaceImports === 'internal'
|
405
|
+
&& !isInternalImport(definition)
|
406
|
+
) {
|
407
|
+
return;
|
408
|
+
}
|
409
|
+
}
|
410
|
+
|
411
|
+
if (isShorthandImportLocal(definition.name)) {
|
412
|
+
if (!options.checkShorthandImports) {
|
413
|
+
return;
|
414
|
+
}
|
415
|
+
|
416
|
+
if (
|
417
|
+
options.checkShorthandImports === 'internal'
|
418
|
+
&& !isInternalImport(definition)
|
419
|
+
) {
|
420
|
+
return;
|
421
|
+
}
|
422
|
+
}
|
423
|
+
|
424
|
+
if (
|
425
|
+
!options.checkShorthandProperties
|
426
|
+
&& isShorthandPropertyValue(definition.name)
|
427
|
+
) {
|
428
|
+
return;
|
429
|
+
}
|
430
|
+
|
431
|
+
const variableReplacements = getNameReplacements(variable.name, options);
|
432
|
+
|
433
|
+
if (variableReplacements.total === 0) {
|
434
|
+
return;
|
435
|
+
}
|
436
|
+
|
437
|
+
const scopes = [
|
438
|
+
...variable.references.map(reference => reference.from),
|
439
|
+
variable.scope,
|
440
|
+
];
|
441
|
+
variableReplacements.samples = variableReplacements.samples.map(
|
442
|
+
name => avoidCapture(name, scopes, isSafeName),
|
443
|
+
);
|
444
|
+
|
445
|
+
const problem = {
|
446
|
+
...getMessage(definition.name.name, variableReplacements, 'variable'),
|
447
|
+
node: definition.name,
|
448
|
+
};
|
449
|
+
|
450
|
+
if (
|
451
|
+
variableReplacements.total === 1
|
452
|
+
&& shouldFix(variable)
|
453
|
+
&& variableReplacements.samples[0]
|
454
|
+
&& !variable.references.some(reference => reference.vueUsedInTemplate)
|
455
|
+
) {
|
456
|
+
const [replacement] = variableReplacements.samples;
|
457
|
+
|
458
|
+
for (const scope of scopes) {
|
459
|
+
if (!scopeToNamesGeneratedByFixer.has(scope)) {
|
460
|
+
scopeToNamesGeneratedByFixer.set(scope, new Set());
|
461
|
+
}
|
462
|
+
|
463
|
+
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
464
|
+
generatedNames.add(replacement);
|
465
|
+
}
|
466
|
+
|
467
|
+
problem.fix = fixer => renameVariable(variable, replacement, fixer);
|
468
|
+
}
|
469
|
+
|
470
|
+
context.report(problem);
|
471
|
+
};
|
472
|
+
|
473
|
+
const checkVariables = scope => {
|
474
|
+
for (const variable of scope.variables) {
|
475
|
+
checkPossiblyWeirdClassVariable(variable);
|
476
|
+
}
|
477
|
+
};
|
478
|
+
|
479
|
+
const checkScope = scope => {
|
480
|
+
const scopes = getScopes(scope);
|
481
|
+
for (const scope of scopes) {
|
482
|
+
checkVariables(scope);
|
483
|
+
}
|
484
|
+
};
|
485
|
+
|
486
|
+
return {
|
487
|
+
Identifier(node) {
|
488
|
+
if (!options.checkProperties) {
|
489
|
+
return;
|
490
|
+
}
|
491
|
+
|
492
|
+
if (node.name === '__proto__') {
|
493
|
+
return;
|
494
|
+
}
|
495
|
+
|
496
|
+
const identifierReplacements = getNameReplacements(node.name, options);
|
497
|
+
|
498
|
+
if (identifierReplacements.total === 0) {
|
499
|
+
return;
|
500
|
+
}
|
501
|
+
|
502
|
+
if (!shouldReportIdentifierAsProperty(node)) {
|
503
|
+
return;
|
504
|
+
}
|
505
|
+
|
506
|
+
const problem = {
|
507
|
+
...getMessage(node.name, identifierReplacements, 'property'),
|
508
|
+
node,
|
509
|
+
};
|
510
|
+
|
511
|
+
context.report(problem);
|
512
|
+
},
|
513
|
+
|
514
|
+
Program(node) {
|
515
|
+
if (!options.checkFilenames) {
|
516
|
+
return;
|
517
|
+
}
|
518
|
+
|
519
|
+
if (
|
520
|
+
filenameWithExtension === '<input>'
|
521
|
+
|| filenameWithExtension === '<text>'
|
522
|
+
) {
|
523
|
+
return;
|
524
|
+
}
|
525
|
+
|
526
|
+
const filename = path.basename(filenameWithExtension);
|
527
|
+
const extension = path.extname(filename);
|
528
|
+
const filenameReplacements = getNameReplacements(path.basename(filename, extension), options);
|
529
|
+
|
530
|
+
if (filenameReplacements.total === 0) {
|
531
|
+
return;
|
532
|
+
}
|
533
|
+
|
534
|
+
filenameReplacements.samples = filenameReplacements.samples.map(replacement => `${replacement}${extension}`);
|
535
|
+
|
536
|
+
context.report({
|
537
|
+
...getMessage(filename, filenameReplacements, 'filename'),
|
538
|
+
node,
|
539
|
+
});
|
540
|
+
},
|
541
|
+
|
542
|
+
'Program:exit'(program) {
|
543
|
+
if (!options.checkVariables) {
|
544
|
+
return;
|
545
|
+
}
|
546
|
+
|
547
|
+
checkScope(context.sourceCode.getScope(program));
|
548
|
+
},
|
549
|
+
};
|
550
|
+
};
|
551
|
+
|
552
|
+
const schema = {
|
553
|
+
type: 'array',
|
554
|
+
additionalItems: false,
|
555
|
+
items: [
|
556
|
+
{
|
557
|
+
type: 'object',
|
558
|
+
additionalProperties: false,
|
559
|
+
properties: {
|
560
|
+
checkProperties: {
|
561
|
+
type: 'boolean',
|
562
|
+
},
|
563
|
+
checkVariables: {
|
564
|
+
type: 'boolean',
|
565
|
+
},
|
566
|
+
checkDefaultAndNamespaceImports: {
|
567
|
+
type: [
|
568
|
+
'boolean',
|
569
|
+
'string',
|
570
|
+
],
|
571
|
+
pattern: 'internal',
|
572
|
+
},
|
573
|
+
checkShorthandImports: {
|
574
|
+
type: [
|
575
|
+
'boolean',
|
576
|
+
'string',
|
577
|
+
],
|
578
|
+
pattern: 'internal',
|
579
|
+
},
|
580
|
+
checkShorthandProperties: {
|
581
|
+
type: 'boolean',
|
582
|
+
},
|
583
|
+
checkFilenames: {
|
584
|
+
type: 'boolean',
|
585
|
+
},
|
586
|
+
extendDefaultReplacements: {
|
587
|
+
type: 'boolean',
|
588
|
+
},
|
589
|
+
replacements: {
|
590
|
+
$ref: '#/definitions/abbreviations',
|
591
|
+
},
|
592
|
+
extendDefaultAllowList: {
|
593
|
+
type: 'boolean',
|
594
|
+
},
|
595
|
+
allowList: {
|
596
|
+
$ref: '#/definitions/booleanObject',
|
597
|
+
},
|
598
|
+
ignore: {
|
599
|
+
type: 'array',
|
600
|
+
uniqueItems: true,
|
601
|
+
},
|
602
|
+
},
|
603
|
+
},
|
604
|
+
],
|
605
|
+
definitions: {
|
606
|
+
abbreviations: {
|
607
|
+
type: 'object',
|
608
|
+
additionalProperties: {
|
609
|
+
$ref: '#/definitions/replacements',
|
610
|
+
},
|
611
|
+
},
|
612
|
+
replacements: {
|
613
|
+
anyOf: [
|
614
|
+
{
|
615
|
+
enum: [
|
616
|
+
false,
|
617
|
+
],
|
618
|
+
},
|
619
|
+
{
|
620
|
+
$ref: '#/definitions/booleanObject',
|
621
|
+
},
|
622
|
+
],
|
623
|
+
},
|
624
|
+
booleanObject: {
|
625
|
+
type: 'object',
|
626
|
+
additionalProperties: {
|
627
|
+
type: 'boolean',
|
628
|
+
},
|
629
|
+
},
|
630
|
+
},
|
631
|
+
};
|
632
|
+
|
633
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
634
|
+
module.exports = {
|
635
|
+
create,
|
636
|
+
meta: {
|
637
|
+
type: 'suggestion',
|
638
|
+
docs: {
|
639
|
+
description: 'Prevent abbreviations.',
|
640
|
+
},
|
641
|
+
fixable: 'code',
|
642
|
+
schema,
|
643
|
+
messages,
|
644
|
+
},
|
645
|
+
};
|