eslint-plugin-unicorn-ts 0.0.1-security → 50.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of eslint-plugin-unicorn-ts might be problematic. Click here for more details.

Files changed (214) hide show
  1. package/configs/all.js +6 -0
  2. package/configs/flat-config-base.js +10 -0
  3. package/configs/legacy-config-base.js +10 -0
  4. package/configs/recommended.js +117 -0
  5. package/index.js +91 -0
  6. package/license +9 -0
  7. package/package.json +186 -4
  8. package/readme.md +356 -0
  9. package/rules/ast/call-or-new-expression.js +127 -0
  10. package/rules/ast/function-types.js +5 -0
  11. package/rules/ast/index.js +39 -0
  12. package/rules/ast/is-arrow-function-body.js +7 -0
  13. package/rules/ast/is-empty-node.js +20 -0
  14. package/rules/ast/is-expression-statement.js +11 -0
  15. package/rules/ast/is-function.js +8 -0
  16. package/rules/ast/is-member-expression.js +101 -0
  17. package/rules/ast/is-method-call.js +65 -0
  18. package/rules/ast/is-reference-identifier.js +156 -0
  19. package/rules/ast/is-static-require.js +14 -0
  20. package/rules/ast/is-undefined.js +7 -0
  21. package/rules/ast/literal.js +29 -0
  22. package/rules/better-regex.js +144 -0
  23. package/rules/catch-error-name.js +136 -0
  24. package/rules/consistent-destructuring.js +168 -0
  25. package/rules/consistent-function-scoping.js +223 -0
  26. package/rules/custom-error-definition.js +215 -0
  27. package/rules/empty-brace-spaces.js +72 -0
  28. package/rules/error-message.js +104 -0
  29. package/rules/escape-case.js +63 -0
  30. package/rules/expiring-todo-comments.js +580 -0
  31. package/rules/explicit-length-check.js +229 -0
  32. package/rules/filename-case.js +258 -0
  33. package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
  34. package/rules/fix/append-argument.js +20 -0
  35. package/rules/fix/extend-fix-range.js +15 -0
  36. package/rules/fix/fix-space-around-keywords.js +35 -0
  37. package/rules/fix/index.js +23 -0
  38. package/rules/fix/remove-argument.js +32 -0
  39. package/rules/fix/remove-member-expression-property.js +11 -0
  40. package/rules/fix/remove-method-call.js +20 -0
  41. package/rules/fix/remove-parentheses.js +11 -0
  42. package/rules/fix/remove-spaces-after.js +14 -0
  43. package/rules/fix/rename-variable.js +9 -0
  44. package/rules/fix/replace-argument.js +8 -0
  45. package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
  46. package/rules/fix/replace-reference-identifier.js +35 -0
  47. package/rules/fix/replace-string-literal.js +11 -0
  48. package/rules/fix/replace-string-raw.js +14 -0
  49. package/rules/fix/replace-template-element.js +11 -0
  50. package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
  51. package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
  52. package/rules/import-style.js +364 -0
  53. package/rules/new-for-builtins.js +85 -0
  54. package/rules/no-abusive-eslint-disable.js +48 -0
  55. package/rules/no-array-callback-reference.js +256 -0
  56. package/rules/no-array-for-each.js +473 -0
  57. package/rules/no-array-method-this-argument.js +188 -0
  58. package/rules/no-array-push-push.js +144 -0
  59. package/rules/no-array-reduce.js +126 -0
  60. package/rules/no-await-expression-member.js +90 -0
  61. package/rules/no-console-spaces.js +86 -0
  62. package/rules/no-document-cookie.js +25 -0
  63. package/rules/no-empty-file.js +57 -0
  64. package/rules/no-for-loop.js +427 -0
  65. package/rules/no-hex-escape.js +46 -0
  66. package/rules/no-instanceof-array.js +65 -0
  67. package/rules/no-invalid-remove-event-listener.js +60 -0
  68. package/rules/no-keyword-prefix.js +199 -0
  69. package/rules/no-lonely-if.js +151 -0
  70. package/rules/no-negated-condition.js +144 -0
  71. package/rules/no-nested-ternary.js +58 -0
  72. package/rules/no-new-array.js +104 -0
  73. package/rules/no-new-buffer.js +98 -0
  74. package/rules/no-null.js +153 -0
  75. package/rules/no-object-as-default-parameter.js +50 -0
  76. package/rules/no-process-exit.js +104 -0
  77. package/rules/no-static-only-class.js +224 -0
  78. package/rules/no-thenable.js +198 -0
  79. package/rules/no-this-assignment.js +38 -0
  80. package/rules/no-typeof-undefined.js +143 -0
  81. package/rules/no-unnecessary-await.js +107 -0
  82. package/rules/no-unnecessary-polyfills.js +176 -0
  83. package/rules/no-unreadable-array-destructuring.js +83 -0
  84. package/rules/no-unreadable-iife.js +45 -0
  85. package/rules/no-unused-properties.js +238 -0
  86. package/rules/no-useless-fallback-in-spread.js +68 -0
  87. package/rules/no-useless-length-check.js +152 -0
  88. package/rules/no-useless-promise-resolve-reject.js +212 -0
  89. package/rules/no-useless-spread.js +381 -0
  90. package/rules/no-useless-switch-case.js +71 -0
  91. package/rules/no-useless-undefined.js +301 -0
  92. package/rules/no-zero-fractions.js +79 -0
  93. package/rules/number-literal-case.js +52 -0
  94. package/rules/numeric-separators-style.js +181 -0
  95. package/rules/prefer-add-event-listener.js +188 -0
  96. package/rules/prefer-array-find.js +423 -0
  97. package/rules/prefer-array-flat-map.js +82 -0
  98. package/rules/prefer-array-flat.js +279 -0
  99. package/rules/prefer-array-index-of.js +32 -0
  100. package/rules/prefer-array-some.js +157 -0
  101. package/rules/prefer-at.js +374 -0
  102. package/rules/prefer-blob-reading-methods.js +45 -0
  103. package/rules/prefer-code-point.js +67 -0
  104. package/rules/prefer-date-now.js +135 -0
  105. package/rules/prefer-default-parameters.js +219 -0
  106. package/rules/prefer-dom-node-append.js +48 -0
  107. package/rules/prefer-dom-node-dataset.js +120 -0
  108. package/rules/prefer-dom-node-remove.js +122 -0
  109. package/rules/prefer-dom-node-text-content.js +75 -0
  110. package/rules/prefer-event-target.js +117 -0
  111. package/rules/prefer-export-from.js +413 -0
  112. package/rules/prefer-includes.js +98 -0
  113. package/rules/prefer-json-parse-buffer.js +159 -0
  114. package/rules/prefer-keyboard-event-key.js +186 -0
  115. package/rules/prefer-logical-operator-over-ternary.js +159 -0
  116. package/rules/prefer-math-trunc.js +109 -0
  117. package/rules/prefer-modern-dom-apis.js +141 -0
  118. package/rules/prefer-modern-math-apis.js +212 -0
  119. package/rules/prefer-module.js +349 -0
  120. package/rules/prefer-native-coercion-functions.js +185 -0
  121. package/rules/prefer-negative-index.js +213 -0
  122. package/rules/prefer-node-protocol.js +61 -0
  123. package/rules/prefer-number-properties.js +126 -0
  124. package/rules/prefer-object-from-entries.js +252 -0
  125. package/rules/prefer-optional-catch-binding.js +75 -0
  126. package/rules/prefer-prototype-methods.js +88 -0
  127. package/rules/prefer-query-selector.js +135 -0
  128. package/rules/prefer-reflect-apply.js +97 -0
  129. package/rules/prefer-regexp-test.js +156 -0
  130. package/rules/prefer-set-has.js +186 -0
  131. package/rules/prefer-set-size.js +103 -0
  132. package/rules/prefer-spread.js +529 -0
  133. package/rules/prefer-string-replace-all.js +145 -0
  134. package/rules/prefer-string-slice.js +182 -0
  135. package/rules/prefer-string-starts-ends-with.js +199 -0
  136. package/rules/prefer-string-trim-start-end.js +44 -0
  137. package/rules/prefer-switch.js +344 -0
  138. package/rules/prefer-ternary.js +282 -0
  139. package/rules/prefer-top-level-await.js +152 -0
  140. package/rules/prefer-type-error.js +151 -0
  141. package/rules/prevent-abbreviations.js +645 -0
  142. package/rules/relative-url-style.js +168 -0
  143. package/rules/require-array-join-separator.js +63 -0
  144. package/rules/require-number-to-fixed-digits-argument.js +54 -0
  145. package/rules/require-post-message-target-origin.js +71 -0
  146. package/rules/shared/abbreviations.js +262 -0
  147. package/rules/shared/dom-events.js +275 -0
  148. package/rules/shared/event-keys.js +52 -0
  149. package/rules/shared/negative-index.js +46 -0
  150. package/rules/shared/simple-array-search-rule.js +128 -0
  151. package/rules/shared/typed-array.js +16 -0
  152. package/rules/string-content.js +187 -0
  153. package/rules/switch-case-braces.js +109 -0
  154. package/rules/template-indent.js +219 -0
  155. package/rules/text-encoding-identifier-case.js +108 -0
  156. package/rules/throw-new-error.js +53 -0
  157. package/rules/utils/array-or-object-prototype-property.js +63 -0
  158. package/rules/utils/assert-token.js +32 -0
  159. package/rules/utils/avoid-capture.js +146 -0
  160. package/rules/utils/boolean.js +92 -0
  161. package/rules/utils/builtins.js +36 -0
  162. package/rules/utils/cartesian-product-samples.js +24 -0
  163. package/rules/utils/create-deprecated-rules.js +25 -0
  164. package/rules/utils/escape-string.js +26 -0
  165. package/rules/utils/escape-template-element-raw.js +6 -0
  166. package/rules/utils/get-ancestor.js +20 -0
  167. package/rules/utils/get-builtin-rule.js +7 -0
  168. package/rules/utils/get-call-expression-arguments-text.js +21 -0
  169. package/rules/utils/get-class-head-location.js +22 -0
  170. package/rules/utils/get-documentation-url.js +10 -0
  171. package/rules/utils/get-indent-string.js +11 -0
  172. package/rules/utils/get-previous-node.js +24 -0
  173. package/rules/utils/get-references.js +9 -0
  174. package/rules/utils/get-scopes.js +14 -0
  175. package/rules/utils/get-switch-case-head-location.js +21 -0
  176. package/rules/utils/get-variable-identifiers.js +7 -0
  177. package/rules/utils/global-reference-tracker.js +72 -0
  178. package/rules/utils/has-optional-chain-element.js +21 -0
  179. package/rules/utils/has-same-range.js +7 -0
  180. package/rules/utils/index.js +53 -0
  181. package/rules/utils/is-function-self-used-inside.js +43 -0
  182. package/rules/utils/is-left-hand-side.js +22 -0
  183. package/rules/utils/is-logical-expression.js +16 -0
  184. package/rules/utils/is-method-named.js +9 -0
  185. package/rules/utils/is-new-expression-with-parentheses.js +26 -0
  186. package/rules/utils/is-node-matches.js +53 -0
  187. package/rules/utils/is-node-value-not-dom-node.js +21 -0
  188. package/rules/utils/is-node-value-not-function.js +42 -0
  189. package/rules/utils/is-number.js +224 -0
  190. package/rules/utils/is-object-method.js +11 -0
  191. package/rules/utils/is-on-same-line.js +7 -0
  192. package/rules/utils/is-same-identifier.js +8 -0
  193. package/rules/utils/is-same-reference.js +173 -0
  194. package/rules/utils/is-shadowed.js +33 -0
  195. package/rules/utils/is-shorthand-export-local.js +9 -0
  196. package/rules/utils/is-shorthand-import-local.js +9 -0
  197. package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
  198. package/rules/utils/is-shorthand-property-value.js +8 -0
  199. package/rules/utils/is-value-not-usable.js +5 -0
  200. package/rules/utils/lodash.js +1589 -0
  201. package/rules/utils/needs-semicolon.js +114 -0
  202. package/rules/utils/numeric.js +53 -0
  203. package/rules/utils/parentheses.js +73 -0
  204. package/rules/utils/resolve-variable-name.js +20 -0
  205. package/rules/utils/rule.js +190 -0
  206. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
  207. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
  208. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
  209. package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
  210. package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
  211. package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
  212. package/rules/utils/singular.js +18 -0
  213. package/rules/utils/to-location.js +21 -0
  214. package/README.md +0 -5
@@ -0,0 +1,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
+ };