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,186 @@
|
|
1
|
+
'use strict';
|
2
|
+
const escapeString = require('./utils/escape-string.js');
|
3
|
+
const translateToKey = require('./shared/event-keys.js');
|
4
|
+
const {isNumberLiteral} = require('./ast/index.js');
|
5
|
+
|
6
|
+
const MESSAGE_ID = 'prefer-keyboard-event-key';
|
7
|
+
const messages = {
|
8
|
+
[MESSAGE_ID]: 'Use `.key` instead of `.{{name}}`.',
|
9
|
+
};
|
10
|
+
|
11
|
+
const keys = new Set([
|
12
|
+
'keyCode',
|
13
|
+
'charCode',
|
14
|
+
'which',
|
15
|
+
]);
|
16
|
+
|
17
|
+
const isPropertyNamedAddEventListener = node =>
|
18
|
+
node?.type === 'CallExpression'
|
19
|
+
&& node.callee.type === 'MemberExpression'
|
20
|
+
&& node.callee.property.name === 'addEventListener';
|
21
|
+
|
22
|
+
const getEventNodeAndReferences = (context, node) => {
|
23
|
+
const eventListener = getMatchingAncestorOfType(node, 'CallExpression', isPropertyNamedAddEventListener);
|
24
|
+
const callback = eventListener?.arguments[1];
|
25
|
+
switch (callback?.type) {
|
26
|
+
case 'ArrowFunctionExpression':
|
27
|
+
case 'FunctionExpression': {
|
28
|
+
const eventVariable = context.sourceCode.getDeclaredVariables(callback)[0];
|
29
|
+
const references = eventVariable?.references;
|
30
|
+
return {
|
31
|
+
event: callback.params[0],
|
32
|
+
references,
|
33
|
+
};
|
34
|
+
}
|
35
|
+
|
36
|
+
default: {
|
37
|
+
return {};
|
38
|
+
}
|
39
|
+
}
|
40
|
+
};
|
41
|
+
|
42
|
+
const isPropertyOf = (node, eventNode) =>
|
43
|
+
node?.parent?.type === 'MemberExpression'
|
44
|
+
&& node.parent.object === eventNode;
|
45
|
+
|
46
|
+
// The third argument is a condition function, as one passed to `Array#filter()`
|
47
|
+
// Helpful if nearest node of type also needs to have some other property
|
48
|
+
const getMatchingAncestorOfType = (node, type, testFunction = () => true) => {
|
49
|
+
let current = node;
|
50
|
+
while (current) {
|
51
|
+
if (current.type === type && testFunction(current)) {
|
52
|
+
return current;
|
53
|
+
}
|
54
|
+
|
55
|
+
current = current.parent;
|
56
|
+
}
|
57
|
+
};
|
58
|
+
|
59
|
+
const getParentByLevel = (node, level) => {
|
60
|
+
let current = node;
|
61
|
+
while (current && level) {
|
62
|
+
level--;
|
63
|
+
current = current.parent;
|
64
|
+
}
|
65
|
+
|
66
|
+
/* c8 ignore next 3 */
|
67
|
+
if (level === 0) {
|
68
|
+
return current;
|
69
|
+
}
|
70
|
+
};
|
71
|
+
|
72
|
+
const fix = node => fixer => {
|
73
|
+
// Since we're only fixing direct property access usages, like `event.keyCode`
|
74
|
+
const nearestIf = getParentByLevel(node, 3);
|
75
|
+
if (!nearestIf || nearestIf.type !== 'IfStatement') {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
const {type, operator, right} = nearestIf.test;
|
80
|
+
if (
|
81
|
+
!(
|
82
|
+
type === 'BinaryExpression'
|
83
|
+
&& (operator === '==' || operator === '===')
|
84
|
+
&& isNumberLiteral(right)
|
85
|
+
)
|
86
|
+
) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
// Either a meta key or a printable character
|
91
|
+
const key = translateToKey[right.value] || String.fromCodePoint(right.value);
|
92
|
+
// And if we recognize the `.keyCode`
|
93
|
+
if (!key) {
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
|
97
|
+
// Apply fixes
|
98
|
+
return [
|
99
|
+
fixer.replaceText(node, 'key'),
|
100
|
+
fixer.replaceText(right, escapeString(key)),
|
101
|
+
];
|
102
|
+
};
|
103
|
+
|
104
|
+
const getProblem = node => ({
|
105
|
+
messageId: MESSAGE_ID,
|
106
|
+
data: {name: node.name},
|
107
|
+
node,
|
108
|
+
fix: fix(node),
|
109
|
+
});
|
110
|
+
|
111
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
112
|
+
const create = context => ({
|
113
|
+
Identifier(node) {
|
114
|
+
if (
|
115
|
+
node.name !== 'keyCode'
|
116
|
+
&& node.name !== 'charCode'
|
117
|
+
&& node.name !== 'which'
|
118
|
+
) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
// Normal case when usage is direct -> `event.keyCode`
|
123
|
+
const {event, references} = getEventNodeAndReferences(context, node);
|
124
|
+
if (!event) {
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
|
128
|
+
if (
|
129
|
+
references
|
130
|
+
&& references.some(reference => isPropertyOf(node, reference.identifier))
|
131
|
+
) {
|
132
|
+
return getProblem(node);
|
133
|
+
}
|
134
|
+
},
|
135
|
+
|
136
|
+
Property(node) {
|
137
|
+
// Destructured case
|
138
|
+
const propertyName = node.value.name;
|
139
|
+
if (!keys.has(propertyName)) {
|
140
|
+
return;
|
141
|
+
}
|
142
|
+
|
143
|
+
const {event, references} = getEventNodeAndReferences(context, node);
|
144
|
+
if (!event) {
|
145
|
+
return;
|
146
|
+
}
|
147
|
+
|
148
|
+
const nearestVariableDeclarator = getMatchingAncestorOfType(
|
149
|
+
node,
|
150
|
+
'VariableDeclarator',
|
151
|
+
);
|
152
|
+
const initObject = nearestVariableDeclarator?.init;
|
153
|
+
|
154
|
+
// Make sure initObject is a reference of eventVariable
|
155
|
+
if (
|
156
|
+
references
|
157
|
+
&& references.some(reference => reference.identifier === initObject)
|
158
|
+
) {
|
159
|
+
return getProblem(node.value);
|
160
|
+
}
|
161
|
+
|
162
|
+
// When the event parameter itself is destructured directly
|
163
|
+
const isEventParameterDestructured = event.type === 'ObjectPattern';
|
164
|
+
if (isEventParameterDestructured) {
|
165
|
+
// Check for properties
|
166
|
+
for (const property of event.properties) {
|
167
|
+
if (property === node) {
|
168
|
+
return getProblem(node.value);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
},
|
173
|
+
});
|
174
|
+
|
175
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
176
|
+
module.exports = {
|
177
|
+
create,
|
178
|
+
meta: {
|
179
|
+
type: 'suggestion',
|
180
|
+
docs: {
|
181
|
+
description: 'Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`.',
|
182
|
+
},
|
183
|
+
fixable: 'code',
|
184
|
+
messages,
|
185
|
+
},
|
186
|
+
};
|
@@ -0,0 +1,159 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isParenthesized, getParenthesizedText} = require('./utils/parentheses.js');
|
3
|
+
const isSameReference = require('./utils/is-same-reference.js');
|
4
|
+
const shouldAddParenthesesToLogicalExpressionChild = require('./utils/should-add-parentheses-to-logical-expression-child.js');
|
5
|
+
const needsSemicolon = require('./utils/needs-semicolon.js');
|
6
|
+
|
7
|
+
const MESSAGE_ID_ERROR = 'prefer-logical-operator-over-ternary/error';
|
8
|
+
const MESSAGE_ID_SUGGESTION = 'prefer-logical-operator-over-ternary/suggestion';
|
9
|
+
const messages = {
|
10
|
+
[MESSAGE_ID_ERROR]: 'Prefer using a logical operator over a ternary.',
|
11
|
+
[MESSAGE_ID_SUGGESTION]: 'Switch to `{{operator}}` operator.',
|
12
|
+
};
|
13
|
+
|
14
|
+
function isSameNode(left, right, sourceCode) {
|
15
|
+
if (isSameReference(left, right)) {
|
16
|
+
return true;
|
17
|
+
}
|
18
|
+
|
19
|
+
if (left.type !== right.type) {
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
|
23
|
+
switch (left.type) {
|
24
|
+
case 'AwaitExpression': {
|
25
|
+
return isSameNode(left.argument, right.argument, sourceCode);
|
26
|
+
}
|
27
|
+
|
28
|
+
case 'LogicalExpression': {
|
29
|
+
return (
|
30
|
+
left.operator === right.operator
|
31
|
+
&& isSameNode(left.left, right.left, sourceCode)
|
32
|
+
&& isSameNode(left.right, right.right, sourceCode)
|
33
|
+
);
|
34
|
+
}
|
35
|
+
|
36
|
+
case 'UnaryExpression': {
|
37
|
+
return (
|
38
|
+
left.operator === right.operator
|
39
|
+
&& left.prefix === right.prefix
|
40
|
+
&& isSameNode(left.argument, right.argument, sourceCode)
|
41
|
+
);
|
42
|
+
}
|
43
|
+
|
44
|
+
case 'UpdateExpression': {
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
|
48
|
+
// No default
|
49
|
+
}
|
50
|
+
|
51
|
+
return sourceCode.getText(left) === sourceCode.getText(right);
|
52
|
+
}
|
53
|
+
|
54
|
+
function fix({
|
55
|
+
fixer,
|
56
|
+
sourceCode,
|
57
|
+
conditionalExpression,
|
58
|
+
left,
|
59
|
+
right,
|
60
|
+
operator,
|
61
|
+
}) {
|
62
|
+
let text = [left, right].map((node, index) => {
|
63
|
+
const isNodeParenthesized = isParenthesized(node, sourceCode);
|
64
|
+
let text = isNodeParenthesized ? getParenthesizedText(node, sourceCode) : sourceCode.getText(node);
|
65
|
+
|
66
|
+
if (
|
67
|
+
!isNodeParenthesized
|
68
|
+
&& shouldAddParenthesesToLogicalExpressionChild(node, {operator, property: index === 0 ? 'left' : 'right'})
|
69
|
+
) {
|
70
|
+
text = `(${text})`;
|
71
|
+
}
|
72
|
+
|
73
|
+
return text;
|
74
|
+
}).join(` ${operator} `);
|
75
|
+
|
76
|
+
// According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
|
77
|
+
// There should be no cases need add parentheses when switching ternary to logical expression
|
78
|
+
|
79
|
+
// ASI
|
80
|
+
if (needsSemicolon(sourceCode.getTokenBefore(conditionalExpression), sourceCode, text)) {
|
81
|
+
text = `;${text}`;
|
82
|
+
}
|
83
|
+
|
84
|
+
return fixer.replaceText(conditionalExpression, text);
|
85
|
+
}
|
86
|
+
|
87
|
+
function getProblem({
|
88
|
+
sourceCode,
|
89
|
+
conditionalExpression,
|
90
|
+
left,
|
91
|
+
right,
|
92
|
+
}) {
|
93
|
+
return {
|
94
|
+
node: conditionalExpression,
|
95
|
+
messageId: MESSAGE_ID_ERROR,
|
96
|
+
suggest: ['??', '||'].map(operator => ({
|
97
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
98
|
+
data: {operator},
|
99
|
+
fix: fixer => fix({
|
100
|
+
fixer,
|
101
|
+
sourceCode,
|
102
|
+
conditionalExpression,
|
103
|
+
left,
|
104
|
+
right,
|
105
|
+
operator,
|
106
|
+
}),
|
107
|
+
})),
|
108
|
+
};
|
109
|
+
}
|
110
|
+
|
111
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
112
|
+
const create = context => {
|
113
|
+
const {sourceCode} = context;
|
114
|
+
|
115
|
+
return {
|
116
|
+
ConditionalExpression(conditionalExpression) {
|
117
|
+
const {test, consequent, alternate} = conditionalExpression;
|
118
|
+
|
119
|
+
// `foo ? foo : bar`
|
120
|
+
if (isSameNode(test, consequent, sourceCode)) {
|
121
|
+
return getProblem({
|
122
|
+
sourceCode,
|
123
|
+
conditionalExpression,
|
124
|
+
left: test,
|
125
|
+
right: alternate,
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
// `!bar ? foo : bar`
|
130
|
+
if (
|
131
|
+
test.type === 'UnaryExpression'
|
132
|
+
&& test.operator === '!'
|
133
|
+
&& test.prefix
|
134
|
+
&& isSameNode(test.argument, alternate, sourceCode)
|
135
|
+
) {
|
136
|
+
return getProblem({
|
137
|
+
sourceCode,
|
138
|
+
conditionalExpression,
|
139
|
+
left: test.argument,
|
140
|
+
right: consequent,
|
141
|
+
});
|
142
|
+
}
|
143
|
+
},
|
144
|
+
};
|
145
|
+
};
|
146
|
+
|
147
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
148
|
+
module.exports = {
|
149
|
+
create,
|
150
|
+
meta: {
|
151
|
+
type: 'suggestion',
|
152
|
+
docs: {
|
153
|
+
description: 'Prefer using a logical operator over a ternary.',
|
154
|
+
},
|
155
|
+
|
156
|
+
hasSuggestions: true,
|
157
|
+
messages,
|
158
|
+
},
|
159
|
+
};
|
@@ -0,0 +1,109 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {hasSideEffect} = require('@eslint-community/eslint-utils');
|
3
|
+
const {fixSpaceAroundKeyword} = require('./fix/index.js');
|
4
|
+
const {isLiteral} = require('./ast/index.js');
|
5
|
+
|
6
|
+
const ERROR_BITWISE = 'error-bitwise';
|
7
|
+
const ERROR_BITWISE_NOT = 'error-bitwise-not';
|
8
|
+
const SUGGESTION_BITWISE = 'suggestion-bitwise';
|
9
|
+
const messages = {
|
10
|
+
[ERROR_BITWISE]: 'Use `Math.trunc` instead of `{{operator}} {{value}}`.',
|
11
|
+
[ERROR_BITWISE_NOT]: 'Use `Math.trunc` instead of `~~`.',
|
12
|
+
[SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.',
|
13
|
+
};
|
14
|
+
|
15
|
+
// Bitwise operators
|
16
|
+
const bitwiseOperators = new Set(['|', '>>', '<<', '^']);
|
17
|
+
const isBitwiseNot = node =>
|
18
|
+
node.type === 'UnaryExpression'
|
19
|
+
&& node.operator === '~';
|
20
|
+
|
21
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
22
|
+
const create = context => {
|
23
|
+
const {sourceCode} = context;
|
24
|
+
|
25
|
+
const mathTruncFunctionCall = node => {
|
26
|
+
const text = sourceCode.getText(node);
|
27
|
+
const parenthesized = node.type === 'SequenceExpression' ? `(${text})` : text;
|
28
|
+
return `Math.trunc(${parenthesized})`;
|
29
|
+
};
|
30
|
+
|
31
|
+
context.on(['BinaryExpression', 'AssignmentExpression'], node => {
|
32
|
+
const {type, operator, right, left} = node;
|
33
|
+
const isAssignment = type === 'AssignmentExpression';
|
34
|
+
if (
|
35
|
+
!isLiteral(right, 0)
|
36
|
+
|| !bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
|
37
|
+
) {
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
|
41
|
+
const problem = {
|
42
|
+
node,
|
43
|
+
messageId: ERROR_BITWISE,
|
44
|
+
data: {
|
45
|
+
operator,
|
46
|
+
value: right.raw,
|
47
|
+
},
|
48
|
+
};
|
49
|
+
|
50
|
+
if (!isAssignment || !hasSideEffect(left, sourceCode)) {
|
51
|
+
const fix = function * (fixer) {
|
52
|
+
const fixed = mathTruncFunctionCall(left);
|
53
|
+
if (isAssignment) {
|
54
|
+
const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
|
55
|
+
yield fixer.replaceText(operatorToken, '=');
|
56
|
+
yield fixer.replaceText(right, fixed);
|
57
|
+
} else {
|
58
|
+
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
|
59
|
+
yield fixer.replaceText(node, fixed);
|
60
|
+
}
|
61
|
+
};
|
62
|
+
|
63
|
+
if (operator === '|') {
|
64
|
+
problem.suggest = [
|
65
|
+
{
|
66
|
+
messageId: SUGGESTION_BITWISE,
|
67
|
+
fix,
|
68
|
+
},
|
69
|
+
];
|
70
|
+
} else {
|
71
|
+
problem.fix = fix;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
return problem;
|
76
|
+
});
|
77
|
+
|
78
|
+
// Unary Expression Selector: Inner-most 2 bitwise NOT
|
79
|
+
context.on('UnaryExpression', node => {
|
80
|
+
if (
|
81
|
+
isBitwiseNot(node)
|
82
|
+
&& isBitwiseNot(node.argument)
|
83
|
+
&& !isBitwiseNot(node.argument.argument)
|
84
|
+
) {
|
85
|
+
return {
|
86
|
+
node,
|
87
|
+
messageId: ERROR_BITWISE_NOT,
|
88
|
+
* fix(fixer) {
|
89
|
+
yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
|
90
|
+
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
|
91
|
+
},
|
92
|
+
};
|
93
|
+
}
|
94
|
+
});
|
95
|
+
};
|
96
|
+
|
97
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
98
|
+
module.exports = {
|
99
|
+
create,
|
100
|
+
meta: {
|
101
|
+
type: 'suggestion',
|
102
|
+
docs: {
|
103
|
+
description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
|
104
|
+
},
|
105
|
+
fixable: 'code',
|
106
|
+
hasSuggestions: true,
|
107
|
+
messages,
|
108
|
+
},
|
109
|
+
};
|
@@ -0,0 +1,141 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isValueNotUsable} = require('./utils/index.js');
|
3
|
+
const {isMethodCall} = require('./ast/index.js');
|
4
|
+
|
5
|
+
const messages = {
|
6
|
+
replaceChildOrInsertBefore:
|
7
|
+
'Prefer `{{oldChildNode}}.{{preferredMethod}}({{newChildNode}})` over `{{parentNode}}.{{method}}({{newChildNode}}, {{oldChildNode}})`.',
|
8
|
+
insertAdjacentTextOrInsertAdjacentElement:
|
9
|
+
'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.',
|
10
|
+
};
|
11
|
+
|
12
|
+
const disallowedMethods = new Map([
|
13
|
+
['replaceChild', 'replaceWith'],
|
14
|
+
['insertBefore', 'before'],
|
15
|
+
]);
|
16
|
+
|
17
|
+
const checkForReplaceChildOrInsertBefore = (context, node) => {
|
18
|
+
const method = node.callee.property.name;
|
19
|
+
const parentNode = node.callee.object.name;
|
20
|
+
const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
|
21
|
+
const preferredMethod = disallowedMethods.get(method);
|
22
|
+
|
23
|
+
const fix = isValueNotUsable(node)
|
24
|
+
? fixer => fixer.replaceText(
|
25
|
+
node,
|
26
|
+
`${oldChildNode}.${preferredMethod}(${newChildNode})`,
|
27
|
+
)
|
28
|
+
: undefined;
|
29
|
+
|
30
|
+
return {
|
31
|
+
node,
|
32
|
+
messageId: 'replaceChildOrInsertBefore',
|
33
|
+
data: {
|
34
|
+
parentNode,
|
35
|
+
method,
|
36
|
+
preferredMethod,
|
37
|
+
newChildNode,
|
38
|
+
oldChildNode,
|
39
|
+
},
|
40
|
+
fix,
|
41
|
+
};
|
42
|
+
};
|
43
|
+
|
44
|
+
const positionReplacers = new Map([
|
45
|
+
['beforebegin', 'before'],
|
46
|
+
['afterbegin', 'prepend'],
|
47
|
+
['beforeend', 'append'],
|
48
|
+
['afterend', 'after'],
|
49
|
+
]);
|
50
|
+
|
51
|
+
const checkForInsertAdjacentTextOrInsertAdjacentElement = (context, node) => {
|
52
|
+
const method = node.callee.property.name;
|
53
|
+
const [positionNode, contentNode] = node.arguments;
|
54
|
+
|
55
|
+
const position = positionNode.value;
|
56
|
+
// Return early when specified position value of first argument is not a recognized value.
|
57
|
+
if (!positionReplacers.has(position)) {
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
|
61
|
+
const preferredMethod = positionReplacers.get(position);
|
62
|
+
const {sourceCode} = context;
|
63
|
+
const content = sourceCode.getText(contentNode);
|
64
|
+
const reference = sourceCode.getText(node.callee.object);
|
65
|
+
|
66
|
+
const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node)
|
67
|
+
? undefined
|
68
|
+
// TODO: make a better fix, don't touch reference
|
69
|
+
: fixer => fixer.replaceText(
|
70
|
+
node,
|
71
|
+
`${reference}.${preferredMethod}(${content})`,
|
72
|
+
);
|
73
|
+
|
74
|
+
return {
|
75
|
+
node,
|
76
|
+
messageId: 'insertAdjacentTextOrInsertAdjacentElement',
|
77
|
+
data: {
|
78
|
+
reference,
|
79
|
+
method,
|
80
|
+
preferredMethod,
|
81
|
+
position: sourceCode.getText(positionNode),
|
82
|
+
content,
|
83
|
+
},
|
84
|
+
fix,
|
85
|
+
};
|
86
|
+
};
|
87
|
+
|
88
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
89
|
+
const create = context => {
|
90
|
+
context.on('CallExpression', node => {
|
91
|
+
if (
|
92
|
+
isMethodCall(node, {
|
93
|
+
methods: ['replaceChild', 'insertBefore'],
|
94
|
+
argumentsLength: 2,
|
95
|
+
optionalCall: false,
|
96
|
+
optionalMember: false,
|
97
|
+
})
|
98
|
+
// We only allow Identifier for now
|
99
|
+
&& node.arguments.every(node => node.type === 'Identifier' && node.name !== 'undefined')
|
100
|
+
// This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
|
101
|
+
&& node.callee.object.type === 'Identifier'
|
102
|
+
) {
|
103
|
+
return checkForReplaceChildOrInsertBefore(context, node);
|
104
|
+
}
|
105
|
+
});
|
106
|
+
|
107
|
+
context.on('CallExpression', node => {
|
108
|
+
if (
|
109
|
+
isMethodCall(node, {
|
110
|
+
methods: ['insertAdjacentText', 'insertAdjacentElement'],
|
111
|
+
argumentsLength: 2,
|
112
|
+
optionalCall: false,
|
113
|
+
optionalMember: false,
|
114
|
+
})
|
115
|
+
// Position argument should be `string`
|
116
|
+
&& node.arguments[0].type === 'Literal'
|
117
|
+
// TODO: remove this limits on second argument
|
118
|
+
&& (
|
119
|
+
node.arguments[1].type === 'Literal'
|
120
|
+
|| node.arguments[1].type === 'Identifier'
|
121
|
+
)
|
122
|
+
// TODO: remove this limits on callee
|
123
|
+
&& node.callee.object.type === 'Identifier'
|
124
|
+
) {
|
125
|
+
return checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
|
126
|
+
}
|
127
|
+
});
|
128
|
+
};
|
129
|
+
|
130
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
131
|
+
module.exports = {
|
132
|
+
create,
|
133
|
+
meta: {
|
134
|
+
type: 'suggestion',
|
135
|
+
docs: {
|
136
|
+
description: 'Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`.',
|
137
|
+
},
|
138
|
+
fixable: 'code',
|
139
|
+
messages,
|
140
|
+
},
|
141
|
+
};
|