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,199 @@
|
|
1
|
+
'use strict';
|
2
|
+
const isShorthandPropertyAssignmentPatternLeft = require('./utils/is-shorthand-property-assignment-pattern-left.js');
|
3
|
+
|
4
|
+
const MESSAGE_ID = 'noKeywordPrefix';
|
5
|
+
const messages = {
|
6
|
+
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.',
|
7
|
+
};
|
8
|
+
|
9
|
+
const prepareOptions = ({
|
10
|
+
disallowedPrefixes,
|
11
|
+
checkProperties = true,
|
12
|
+
onlyCamelCase = true,
|
13
|
+
} = {}) => ({
|
14
|
+
disallowedPrefixes: (disallowedPrefixes || [
|
15
|
+
'new',
|
16
|
+
'class',
|
17
|
+
]),
|
18
|
+
checkProperties,
|
19
|
+
onlyCamelCase,
|
20
|
+
});
|
21
|
+
|
22
|
+
function findKeywordPrefix(name, options) {
|
23
|
+
return options.disallowedPrefixes.find(keyword => {
|
24
|
+
const suffix = options.onlyCamelCase ? '[A-Z]' : '.';
|
25
|
+
const regex = new RegExp(`^${keyword}${suffix}`);
|
26
|
+
return name.match(regex);
|
27
|
+
});
|
28
|
+
}
|
29
|
+
|
30
|
+
function checkMemberExpression(report, node, options) {
|
31
|
+
const {name, parent} = node;
|
32
|
+
const keyword = findKeywordPrefix(name, options);
|
33
|
+
const effectiveParent = parent.type === 'MemberExpression' ? parent.parent : parent;
|
34
|
+
|
35
|
+
if (!options.checkProperties) {
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
|
39
|
+
if (parent.object.type === 'Identifier' && parent.object.name === name && Boolean(keyword)) {
|
40
|
+
report(node, keyword);
|
41
|
+
} else if (
|
42
|
+
effectiveParent.type === 'AssignmentExpression'
|
43
|
+
&& Boolean(keyword)
|
44
|
+
&& (effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression')
|
45
|
+
&& effectiveParent.left.property.name === name
|
46
|
+
) {
|
47
|
+
report(node, keyword);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function checkObjectPattern(report, node, options) {
|
52
|
+
const {name, parent} = node;
|
53
|
+
const keyword = findKeywordPrefix(name, options);
|
54
|
+
|
55
|
+
/* c8 ignore next 3 */
|
56
|
+
if (parent.shorthand && parent.value.left && Boolean(keyword)) {
|
57
|
+
report(node, keyword);
|
58
|
+
}
|
59
|
+
|
60
|
+
const assignmentKeyEqualsValue = parent.key.name === parent.value.name;
|
61
|
+
|
62
|
+
if (Boolean(keyword) && parent.computed) {
|
63
|
+
report(node, keyword);
|
64
|
+
}
|
65
|
+
|
66
|
+
// Prevent checking right hand side of destructured object
|
67
|
+
if (parent.key === node && parent.value !== node) {
|
68
|
+
return true;
|
69
|
+
}
|
70
|
+
|
71
|
+
const valueIsInvalid = parent.value.name && Boolean(keyword);
|
72
|
+
|
73
|
+
// Ignore destructuring if the option is set, unless a new identifier is created
|
74
|
+
if (valueIsInvalid && !assignmentKeyEqualsValue) {
|
75
|
+
report(node, keyword);
|
76
|
+
}
|
77
|
+
|
78
|
+
return false;
|
79
|
+
}
|
80
|
+
|
81
|
+
// Core logic copied from:
|
82
|
+
// https://github.com/eslint/eslint/blob/master/lib/rules/camelcase.js
|
83
|
+
const create = context => {
|
84
|
+
const options = prepareOptions(context.options[0]);
|
85
|
+
|
86
|
+
// Contains reported nodes to avoid reporting twice on destructuring with shorthand notation
|
87
|
+
const reported = [];
|
88
|
+
const ALLOWED_PARENT_TYPES = new Set(['CallExpression', 'NewExpression']);
|
89
|
+
|
90
|
+
function report(node, keyword) {
|
91
|
+
if (!reported.includes(node)) {
|
92
|
+
reported.push(node);
|
93
|
+
context.report({
|
94
|
+
node,
|
95
|
+
messageId: MESSAGE_ID,
|
96
|
+
data: {
|
97
|
+
name: node.name,
|
98
|
+
keyword,
|
99
|
+
},
|
100
|
+
});
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
return {
|
105
|
+
Identifier(node) {
|
106
|
+
const {name, parent} = node;
|
107
|
+
const keyword = findKeywordPrefix(name, options);
|
108
|
+
const effectiveParent = parent.type === 'MemberExpression' ? parent.parent : parent;
|
109
|
+
|
110
|
+
if (parent.type === 'MemberExpression') {
|
111
|
+
checkMemberExpression(report, node, options);
|
112
|
+
} else if (
|
113
|
+
parent.type === 'Property'
|
114
|
+
|| parent.type === 'AssignmentPattern'
|
115
|
+
) {
|
116
|
+
if (parent.parent.type === 'ObjectPattern') {
|
117
|
+
const finished = checkObjectPattern(report, node, options);
|
118
|
+
if (finished) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
if (
|
124
|
+
!options.checkProperties
|
125
|
+
) {
|
126
|
+
return;
|
127
|
+
}
|
128
|
+
|
129
|
+
// Don't check right hand side of AssignmentExpression to prevent duplicate warnings
|
130
|
+
if (
|
131
|
+
Boolean(keyword)
|
132
|
+
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
|
133
|
+
&& !(parent.right === node)
|
134
|
+
&& !isShorthandPropertyAssignmentPatternLeft(node)
|
135
|
+
) {
|
136
|
+
report(node, keyword);
|
137
|
+
}
|
138
|
+
|
139
|
+
// Check if it's an import specifier
|
140
|
+
} else if (
|
141
|
+
[
|
142
|
+
'ImportSpecifier',
|
143
|
+
'ImportNamespaceSpecifier',
|
144
|
+
'ImportDefaultSpecifier',
|
145
|
+
].includes(parent.type)
|
146
|
+
) {
|
147
|
+
// Report only if the local imported identifier is invalid
|
148
|
+
if (Boolean(keyword) && parent.local?.name === name) {
|
149
|
+
report(node, keyword);
|
150
|
+
}
|
151
|
+
|
152
|
+
// Report anything that is invalid that isn't a CallExpression
|
153
|
+
} else if (
|
154
|
+
Boolean(keyword)
|
155
|
+
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
|
156
|
+
) {
|
157
|
+
report(node, keyword);
|
158
|
+
}
|
159
|
+
},
|
160
|
+
};
|
161
|
+
};
|
162
|
+
|
163
|
+
const schema = [
|
164
|
+
{
|
165
|
+
type: 'object',
|
166
|
+
additionalProperties: false,
|
167
|
+
properties: {
|
168
|
+
disallowedPrefixes: {
|
169
|
+
type: 'array',
|
170
|
+
items: [
|
171
|
+
{
|
172
|
+
type: 'string',
|
173
|
+
},
|
174
|
+
],
|
175
|
+
minItems: 0,
|
176
|
+
uniqueItems: true,
|
177
|
+
},
|
178
|
+
checkProperties: {
|
179
|
+
type: 'boolean',
|
180
|
+
},
|
181
|
+
onlyCamelCase: {
|
182
|
+
type: 'boolean',
|
183
|
+
},
|
184
|
+
},
|
185
|
+
},
|
186
|
+
];
|
187
|
+
|
188
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
189
|
+
module.exports = {
|
190
|
+
create,
|
191
|
+
meta: {
|
192
|
+
type: 'suggestion',
|
193
|
+
docs: {
|
194
|
+
description: 'Disallow identifiers starting with `new` or `class`.',
|
195
|
+
},
|
196
|
+
schema,
|
197
|
+
messages,
|
198
|
+
},
|
199
|
+
};
|
@@ -0,0 +1,151 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isParenthesized, isNotSemicolonToken} = require('@eslint-community/eslint-utils');
|
3
|
+
const {needsSemicolon} = require('./utils/index.js');
|
4
|
+
const {removeSpacesAfter} = require('./fix/index.js');
|
5
|
+
|
6
|
+
const MESSAGE_ID = 'no-lonely-if';
|
7
|
+
const messages = {
|
8
|
+
[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.',
|
9
|
+
};
|
10
|
+
|
11
|
+
const isIfStatementWithoutAlternate = node => node.type === 'IfStatement' && !node.alternate;
|
12
|
+
|
13
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
|
14
|
+
// Lower precedence than `&&`
|
15
|
+
const needParenthesis = node => (
|
16
|
+
(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??'))
|
17
|
+
|| node.type === 'ConditionalExpression'
|
18
|
+
|| node.type === 'AssignmentExpression'
|
19
|
+
|| node.type === 'YieldExpression'
|
20
|
+
|| node.type === 'SequenceExpression'
|
21
|
+
);
|
22
|
+
|
23
|
+
function getIfStatementTokens(node, sourceCode) {
|
24
|
+
const tokens = {};
|
25
|
+
|
26
|
+
tokens.ifToken = sourceCode.getFirstToken(node);
|
27
|
+
tokens.openingParenthesisToken = sourceCode.getFirstToken(node, 1);
|
28
|
+
|
29
|
+
const {consequent} = node;
|
30
|
+
tokens.closingParenthesisToken = sourceCode.getTokenBefore(consequent);
|
31
|
+
|
32
|
+
if (consequent.type === 'BlockStatement') {
|
33
|
+
tokens.openingBraceToken = sourceCode.getFirstToken(consequent);
|
34
|
+
tokens.closingBraceToken = sourceCode.getLastToken(consequent);
|
35
|
+
}
|
36
|
+
|
37
|
+
return tokens;
|
38
|
+
}
|
39
|
+
|
40
|
+
function fix(innerIfStatement, sourceCode) {
|
41
|
+
return function * (fixer) {
|
42
|
+
const outerIfStatement = (
|
43
|
+
innerIfStatement.parent.type === 'BlockStatement'
|
44
|
+
? innerIfStatement.parent
|
45
|
+
: innerIfStatement
|
46
|
+
).parent;
|
47
|
+
const outer = {
|
48
|
+
...outerIfStatement,
|
49
|
+
...getIfStatementTokens(outerIfStatement, sourceCode),
|
50
|
+
};
|
51
|
+
const inner = {
|
52
|
+
...innerIfStatement,
|
53
|
+
...getIfStatementTokens(innerIfStatement, sourceCode),
|
54
|
+
};
|
55
|
+
|
56
|
+
// Remove inner `if` token
|
57
|
+
yield fixer.remove(inner.ifToken);
|
58
|
+
yield removeSpacesAfter(inner.ifToken, sourceCode, fixer);
|
59
|
+
|
60
|
+
// Remove outer `{}`
|
61
|
+
if (outer.openingBraceToken) {
|
62
|
+
yield fixer.remove(outer.openingBraceToken);
|
63
|
+
yield removeSpacesAfter(outer.openingBraceToken, sourceCode, fixer);
|
64
|
+
yield fixer.remove(outer.closingBraceToken);
|
65
|
+
|
66
|
+
const tokenBefore = sourceCode.getTokenBefore(outer.closingBraceToken, {includeComments: true});
|
67
|
+
yield removeSpacesAfter(tokenBefore, sourceCode, fixer);
|
68
|
+
}
|
69
|
+
|
70
|
+
// Add new `()`
|
71
|
+
yield fixer.insertTextBefore(outer.openingParenthesisToken, '(');
|
72
|
+
yield fixer.insertTextAfter(
|
73
|
+
inner.closingParenthesisToken,
|
74
|
+
`)${inner.consequent.type === 'EmptyStatement' ? '' : ' '}`,
|
75
|
+
);
|
76
|
+
|
77
|
+
// Add ` && `
|
78
|
+
yield fixer.insertTextAfter(outer.closingParenthesisToken, ' && ');
|
79
|
+
|
80
|
+
// Remove `()` if `test` don't need it
|
81
|
+
for (const {test, openingParenthesisToken, closingParenthesisToken} of [outer, inner]) {
|
82
|
+
if (
|
83
|
+
isParenthesized(test, sourceCode)
|
84
|
+
|| !needParenthesis(test)
|
85
|
+
) {
|
86
|
+
yield fixer.remove(openingParenthesisToken);
|
87
|
+
yield fixer.remove(closingParenthesisToken);
|
88
|
+
}
|
89
|
+
|
90
|
+
yield removeSpacesAfter(closingParenthesisToken, sourceCode, fixer);
|
91
|
+
}
|
92
|
+
|
93
|
+
// If the `if` statement has no block, and is not followed by a semicolon,
|
94
|
+
// make sure that fixing the issue would not change semantics due to ASI.
|
95
|
+
// Similar logic https://github.com/eslint/eslint/blob/2124e1b5dad30a905dc26bde9da472bf622d3f50/lib/rules/no-lonely-if.js#L61-L77
|
96
|
+
if (inner.consequent.type !== 'BlockStatement') {
|
97
|
+
const lastToken = sourceCode.getLastToken(inner.consequent);
|
98
|
+
if (isNotSemicolonToken(lastToken)) {
|
99
|
+
const nextToken = sourceCode.getTokenAfter(outer);
|
100
|
+
if (nextToken && needsSemicolon(lastToken, sourceCode, nextToken.value)) {
|
101
|
+
yield fixer.insertTextBefore(nextToken, ';');
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
};
|
106
|
+
}
|
107
|
+
|
108
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
109
|
+
const create = context => ({
|
110
|
+
IfStatement(ifStatement) {
|
111
|
+
if (!(
|
112
|
+
isIfStatementWithoutAlternate(ifStatement)
|
113
|
+
&& (
|
114
|
+
// `if (a) { if (b) {} }`
|
115
|
+
(
|
116
|
+
ifStatement.parent.type === 'BlockStatement'
|
117
|
+
&& ifStatement.parent.body.length === 1
|
118
|
+
&& ifStatement.parent.body[0] === ifStatement
|
119
|
+
&& isIfStatementWithoutAlternate(ifStatement.parent.parent)
|
120
|
+
&& ifStatement.parent.parent.consequent === ifStatement.parent
|
121
|
+
)
|
122
|
+
// `if (a) if (b) {}`
|
123
|
+
|| (
|
124
|
+
isIfStatementWithoutAlternate(ifStatement.parent)
|
125
|
+
&& ifStatement.parent.consequent === ifStatement
|
126
|
+
)
|
127
|
+
)
|
128
|
+
)) {
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
|
132
|
+
return {
|
133
|
+
node: ifStatement,
|
134
|
+
messageId: MESSAGE_ID,
|
135
|
+
fix: fix(ifStatement, context.sourceCode),
|
136
|
+
};
|
137
|
+
},
|
138
|
+
});
|
139
|
+
|
140
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
141
|
+
module.exports = {
|
142
|
+
create,
|
143
|
+
meta: {
|
144
|
+
type: 'suggestion',
|
145
|
+
docs: {
|
146
|
+
description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.',
|
147
|
+
},
|
148
|
+
fixable: 'code',
|
149
|
+
messages,
|
150
|
+
},
|
151
|
+
};
|
@@ -0,0 +1,144 @@
|
|
1
|
+
/*
|
2
|
+
Based on ESLint builtin `no-negated-condition` rule
|
3
|
+
https://github.com/eslint/eslint/blob/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c/lib/rules/no-negated-condition.js
|
4
|
+
*/
|
5
|
+
'use strict';
|
6
|
+
const {
|
7
|
+
removeParentheses,
|
8
|
+
fixSpaceAroundKeyword,
|
9
|
+
addParenthesizesToReturnOrThrowExpression,
|
10
|
+
} = require('./fix/index.js');
|
11
|
+
const {
|
12
|
+
getParenthesizedRange,
|
13
|
+
isParenthesized,
|
14
|
+
} = require('./utils/parentheses.js');
|
15
|
+
const isOnSameLine = require('./utils/is-on-same-line.js');
|
16
|
+
const needsSemicolon = require('./utils/needs-semicolon.js');
|
17
|
+
|
18
|
+
const MESSAGE_ID = 'no-negated-condition';
|
19
|
+
const messages = {
|
20
|
+
[MESSAGE_ID]: 'Unexpected negated condition.',
|
21
|
+
};
|
22
|
+
|
23
|
+
function * convertNegatedCondition(fixer, node, sourceCode) {
|
24
|
+
const {test} = node;
|
25
|
+
if (test.type === 'UnaryExpression') {
|
26
|
+
const token = sourceCode.getFirstToken(test);
|
27
|
+
|
28
|
+
if (node.type === 'IfStatement') {
|
29
|
+
yield * removeParentheses(test.argument, fixer, sourceCode);
|
30
|
+
}
|
31
|
+
|
32
|
+
yield fixer.remove(token);
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
|
36
|
+
const token = sourceCode.getTokenAfter(
|
37
|
+
test.left,
|
38
|
+
token => token.type === 'Punctuator' && token.value === test.operator,
|
39
|
+
);
|
40
|
+
|
41
|
+
yield fixer.replaceText(token, '=' + token.value.slice(1));
|
42
|
+
}
|
43
|
+
|
44
|
+
function * swapConsequentAndAlternate(fixer, node, sourceCode) {
|
45
|
+
const isIfStatement = node.type === 'IfStatement';
|
46
|
+
const [consequent, alternate] = [
|
47
|
+
node.consequent,
|
48
|
+
node.alternate,
|
49
|
+
].map(node => {
|
50
|
+
const range = getParenthesizedRange(node, sourceCode);
|
51
|
+
let text = sourceCode.text.slice(...range);
|
52
|
+
// `if (!a) b(); else c()` can't fix to `if (!a) c() else b();`
|
53
|
+
if (isIfStatement && node.type !== 'BlockStatement') {
|
54
|
+
text = `{${text}}`;
|
55
|
+
}
|
56
|
+
|
57
|
+
return {
|
58
|
+
range,
|
59
|
+
text,
|
60
|
+
};
|
61
|
+
});
|
62
|
+
|
63
|
+
if (consequent.text === alternate.text) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
yield fixer.replaceTextRange(consequent.range, alternate.text);
|
68
|
+
yield fixer.replaceTextRange(alternate.range, consequent.text);
|
69
|
+
}
|
70
|
+
|
71
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
72
|
+
const create = context => {
|
73
|
+
context.on(['IfStatement', 'ConditionalExpression'], node => {
|
74
|
+
if (
|
75
|
+
node.type === 'IfStatement'
|
76
|
+
&& (
|
77
|
+
!node.alternate
|
78
|
+
|| node.alternate.type === 'IfStatement'
|
79
|
+
)
|
80
|
+
) {
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
|
84
|
+
const {test} = node;
|
85
|
+
|
86
|
+
if (!(
|
87
|
+
(test.type === 'UnaryExpression' && test.operator === '!')
|
88
|
+
|| (test.type === 'BinaryExpression' && (test.operator === '!=' || test.operator === '!=='))
|
89
|
+
)) {
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
|
93
|
+
return {
|
94
|
+
node: test,
|
95
|
+
messageId: MESSAGE_ID,
|
96
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
97
|
+
* fix(fixer) {
|
98
|
+
const {sourceCode} = context;
|
99
|
+
yield * convertNegatedCondition(fixer, node, sourceCode);
|
100
|
+
yield * swapConsequentAndAlternate(fixer, node, sourceCode);
|
101
|
+
|
102
|
+
if (
|
103
|
+
node.type !== 'ConditionalExpression'
|
104
|
+
|| test.type !== 'UnaryExpression'
|
105
|
+
) {
|
106
|
+
return;
|
107
|
+
}
|
108
|
+
|
109
|
+
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
|
110
|
+
|
111
|
+
const {parent} = node;
|
112
|
+
const [firstToken, secondToken] = sourceCode.getFirstTokens(test, 2);
|
113
|
+
if (
|
114
|
+
(parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement')
|
115
|
+
&& parent.argument === node
|
116
|
+
&& !isOnSameLine(firstToken, secondToken)
|
117
|
+
&& !isParenthesized(node, sourceCode)
|
118
|
+
&& !isParenthesized(test, sourceCode)
|
119
|
+
) {
|
120
|
+
yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode);
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
|
124
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
125
|
+
if (needsSemicolon(tokenBefore, sourceCode, secondToken.value)) {
|
126
|
+
yield fixer.insertTextBefore(node, ';');
|
127
|
+
}
|
128
|
+
},
|
129
|
+
};
|
130
|
+
});
|
131
|
+
};
|
132
|
+
|
133
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
134
|
+
module.exports = {
|
135
|
+
create,
|
136
|
+
meta: {
|
137
|
+
type: 'suggestion',
|
138
|
+
docs: {
|
139
|
+
description: 'Disallow negated conditions.',
|
140
|
+
},
|
141
|
+
fixable: 'code',
|
142
|
+
messages,
|
143
|
+
},
|
144
|
+
};
|
@@ -0,0 +1,58 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isParenthesized} = require('@eslint-community/eslint-utils');
|
3
|
+
|
4
|
+
const MESSAGE_ID_TOO_DEEP = 'too-deep';
|
5
|
+
const MESSAGE_ID_SHOULD_PARENTHESIZED = 'should-parenthesized';
|
6
|
+
const messages = {
|
7
|
+
[MESSAGE_ID_TOO_DEEP]: 'Do not nest ternary expressions.',
|
8
|
+
[MESSAGE_ID_SHOULD_PARENTHESIZED]: 'Nest ternary expression should be parenthesized.',
|
9
|
+
};
|
10
|
+
|
11
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
12
|
+
const create = context => ({
|
13
|
+
ConditionalExpression(node) {
|
14
|
+
if ([
|
15
|
+
node.test,
|
16
|
+
node.consequent,
|
17
|
+
node.alternate,
|
18
|
+
].some(node => node.type === 'ConditionalExpression')) {
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
const {sourceCode} = context;
|
23
|
+
const ancestors = sourceCode.getAncestors(node).reverse();
|
24
|
+
const nestLevel = ancestors.findIndex(node => node.type !== 'ConditionalExpression');
|
25
|
+
|
26
|
+
if (nestLevel === 1 && !isParenthesized(node, sourceCode)) {
|
27
|
+
return {
|
28
|
+
node,
|
29
|
+
messageId: MESSAGE_ID_SHOULD_PARENTHESIZED,
|
30
|
+
fix: fixer => [
|
31
|
+
fixer.insertTextBefore(node, '('),
|
32
|
+
fixer.insertTextAfter(node, ')'),
|
33
|
+
],
|
34
|
+
};
|
35
|
+
}
|
36
|
+
|
37
|
+
// Nesting more than one level not allowed
|
38
|
+
if (nestLevel > 1) {
|
39
|
+
return {
|
40
|
+
node: nestLevel > 2 ? ancestors[nestLevel - 3] : node,
|
41
|
+
messageId: MESSAGE_ID_TOO_DEEP,
|
42
|
+
};
|
43
|
+
}
|
44
|
+
},
|
45
|
+
});
|
46
|
+
|
47
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
48
|
+
module.exports = {
|
49
|
+
create,
|
50
|
+
meta: {
|
51
|
+
type: 'suggestion',
|
52
|
+
docs: {
|
53
|
+
description: 'Disallow nested ternary expressions.',
|
54
|
+
},
|
55
|
+
fixable: 'code',
|
56
|
+
messages,
|
57
|
+
},
|
58
|
+
};
|
@@ -0,0 +1,104 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
|
3
|
+
const needsSemicolon = require('./utils/needs-semicolon.js');
|
4
|
+
const isNumber = require('./utils/is-number.js');
|
5
|
+
const {isNewExpression} = require('./ast/index.js');
|
6
|
+
|
7
|
+
const MESSAGE_ID_ERROR = 'error';
|
8
|
+
const MESSAGE_ID_LENGTH = 'array-length';
|
9
|
+
const MESSAGE_ID_ONLY_ELEMENT = 'only-element';
|
10
|
+
const MESSAGE_ID_SPREAD = 'spread';
|
11
|
+
const messages = {
|
12
|
+
[MESSAGE_ID_ERROR]: 'Do not use `new Array()`.',
|
13
|
+
[MESSAGE_ID_LENGTH]: 'The argument is the length of array.',
|
14
|
+
[MESSAGE_ID_ONLY_ELEMENT]: 'The argument is the only element of array.',
|
15
|
+
[MESSAGE_ID_SPREAD]: 'Spread the argument.',
|
16
|
+
};
|
17
|
+
|
18
|
+
function getProblem(context, node) {
|
19
|
+
if (
|
20
|
+
!isNewExpression(node, {
|
21
|
+
name: 'Array',
|
22
|
+
argumentsLength: 1,
|
23
|
+
allowSpreadElement: true,
|
24
|
+
})
|
25
|
+
) {
|
26
|
+
return;
|
27
|
+
}
|
28
|
+
|
29
|
+
const problem = {
|
30
|
+
node,
|
31
|
+
messageId: MESSAGE_ID_ERROR,
|
32
|
+
};
|
33
|
+
|
34
|
+
const [argumentNode] = node.arguments;
|
35
|
+
|
36
|
+
const {sourceCode} = context;
|
37
|
+
let text = sourceCode.getText(argumentNode);
|
38
|
+
if (isParenthesized(argumentNode, sourceCode)) {
|
39
|
+
text = `(${text})`;
|
40
|
+
}
|
41
|
+
|
42
|
+
const maybeSemiColon = needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')
|
43
|
+
? ';'
|
44
|
+
: '';
|
45
|
+
|
46
|
+
// We are not sure how many `arguments` passed
|
47
|
+
if (argumentNode.type === 'SpreadElement') {
|
48
|
+
problem.suggest = [
|
49
|
+
{
|
50
|
+
messageId: MESSAGE_ID_SPREAD,
|
51
|
+
fix: fixer => fixer.replaceText(node, `${maybeSemiColon}[${text}]`),
|
52
|
+
},
|
53
|
+
];
|
54
|
+
return problem;
|
55
|
+
}
|
56
|
+
|
57
|
+
const fromLengthText = `Array.from(${text === 'length' ? '{length}' : `{length: ${text}}`})`;
|
58
|
+
const scope = sourceCode.getScope(node);
|
59
|
+
if (isNumber(argumentNode, scope)) {
|
60
|
+
problem.fix = fixer => fixer.replaceText(node, fromLengthText);
|
61
|
+
return problem;
|
62
|
+
}
|
63
|
+
|
64
|
+
const onlyElementText = `${maybeSemiColon}[${text}]`;
|
65
|
+
const result = getStaticValue(argumentNode, scope);
|
66
|
+
if (result !== null && typeof result.value !== 'number') {
|
67
|
+
problem.fix = fixer => fixer.replaceText(node, onlyElementText);
|
68
|
+
return problem;
|
69
|
+
}
|
70
|
+
|
71
|
+
// We don't know the argument is number or not
|
72
|
+
problem.suggest = [
|
73
|
+
{
|
74
|
+
messageId: MESSAGE_ID_LENGTH,
|
75
|
+
fix: fixer => fixer.replaceText(node, fromLengthText),
|
76
|
+
},
|
77
|
+
{
|
78
|
+
messageId: MESSAGE_ID_ONLY_ELEMENT,
|
79
|
+
fix: fixer => fixer.replaceText(node, onlyElementText),
|
80
|
+
},
|
81
|
+
];
|
82
|
+
return problem;
|
83
|
+
}
|
84
|
+
|
85
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
86
|
+
const create = context => ({
|
87
|
+
NewExpression(node) {
|
88
|
+
return getProblem(context, node);
|
89
|
+
},
|
90
|
+
});
|
91
|
+
|
92
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
93
|
+
module.exports = {
|
94
|
+
create,
|
95
|
+
meta: {
|
96
|
+
type: 'suggestion',
|
97
|
+
docs: {
|
98
|
+
description: 'Disallow `new Array()`.',
|
99
|
+
},
|
100
|
+
fixable: 'code',
|
101
|
+
hasSuggestions: true,
|
102
|
+
messages,
|
103
|
+
},
|
104
|
+
};
|